From 4b3fb06c9027d6383e12b77a5ad9629af6c63869 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Thu, 30 Nov 2023 15:09:33 +0530 Subject: [PATCH 1/5] NMC 2172 - dashboard theming customisation --- iOSClient/Favorites/NCFavorite.swift | 1 + iOSClient/Files/NCFiles.swift | 289 +++--- .../Cell/NCCellProtocol.swift | 6 + .../Collection Common/Cell/NCListCell.swift | 108 ++- .../Collection Common/Cell/NCListCell.xib | 331 +++---- ...nViewCommon+CollectionViewDataSource.swift | 313 +++++-- ...ctionViewCommon+SelectTabBarDelegate.swift | 163 +++- ...mmon+SwipeCollectionViewCellDelegate.swift | 94 ++ .../NCCollectionViewCommon.swift | 870 ++++++++++++++---- .../NCSectionFirstHeader.swift | 195 +++- .../NCSectionFirstHeader.xib | 232 +++-- iOSClient/Menu/NCMenuAction.swift | 15 + iOSClient/Menu/NCSortMenu.swift | 149 +++ iOSClient/Networking/NCDownloadAction.swift | 735 +++++++++++++++ iOSClient/Offline/NCOffline.swift | 1 + 15 files changed, 2795 insertions(+), 707 deletions(-) create mode 100644 iOSClient/Main/Collection Common/NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift create mode 100644 iOSClient/Menu/NCSortMenu.swift create mode 100644 iOSClient/Networking/NCDownloadAction.swift diff --git a/iOSClient/Favorites/NCFavorite.swift b/iOSClient/Favorites/NCFavorite.swift index cb5bd4cfb6..d386b84d5b 100644 --- a/iOSClient/Favorites/NCFavorite.swift +++ b/iOSClient/Favorites/NCFavorite.swift @@ -12,6 +12,7 @@ class NCFavorite: NCCollectionViewCommon { titleCurrentFolder = NSLocalizedString("_favorites_", comment: "") layoutKey = NCGlobal.shared.layoutViewFavorite enableSearchBar = false + headerMenuButtonsView = true headerRichWorkspaceDisable = true emptyImageName = "star.fill" emptyImageColors = [NCBrandColor.shared.yellowFavorite] diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index 2a443ab17d..d0d231fb17 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2020 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCFiles.swift +// Nextcloud +// +// Created by Marino Faggiana on 26/09/2020. +// Copyright © 2020 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// import UIKit import NextcloudKit @@ -8,6 +27,8 @@ import RealmSwift import SwiftUI class NCFiles: NCCollectionViewCommon { + @IBOutlet weak var plusButton: UIButton! + internal var fileNameBlink: String? internal var fileNameOpen: String? @@ -31,63 +52,66 @@ class NCFiles: NCCollectionViewCommon { override func viewDidLoad() { super.viewDidLoad() - NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeTheming), object: nil, queue: nil) { notification in - Task { @MainActor in - if let userInfo = notification.userInfo, - let account = userInfo["account"] as? String, - self.controller?.account == account { - let color = NCBrandColor.shared.getElement(account: account) - self.mainNavigationController?.menuToolbar.items?.forEach { $0.tintColor = color } - } - } - } + /// Plus Button + let image = UIImage(systemName: "plus", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [.white])) - NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in - self.stopSyncMetadata() + plusButton.setTitle("", for: .normal) + plusButton.setImage(image, for: .normal) + plusButton.backgroundColor = NCBrandColor.shared.customer + if let activeTableAccount = NCManageDatabase.shared.getActiveTableAccount() { + self.plusButton.backgroundColor = NCBrandColor.shared.getElement(account: activeTableAccount.account) + } + plusButton.accessibilityLabel = NSLocalizedString("_accessibility_add_upload_", comment: "") + plusButton.layer.cornerRadius = plusButton.frame.size.width / 2.0 + plusButton.layer.masksToBounds = false + plusButton.layer.shadowOffset = CGSize(width: 0, height: 0) + plusButton.layer.shadowRadius = 3.0 + plusButton.layer.shadowOpacity = 0.5 + + NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeTheming), object: nil, queue: nil) { _ in + if let activeTableAccount = NCManageDatabase.shared.getActiveTableAccount() { + self.plusButton.backgroundColor = NCBrandColor.shared.getElement(account: activeTableAccount.account) + } } if self.serverUrl.isEmpty { - // - // Set ServerURL when start (isEmpty) - // + + /// + /// Set ServerURL when start (isEmpty) + /// self.serverUrl = utilityFileSystem.getHomeServer(session: session) self.titleCurrentFolder = getNavigationTitle() NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeUser), object: nil, queue: nil) { notification in - Task { @MainActor in - if let userInfo = notification.userInfo, - let controller = userInfo["controller"] as? NCMainTabBarController { - guard controller == self.controller else { - return - } - } - if let userInfo = notification.userInfo, - let account = userInfo["account"] as? String { - let color = NCBrandColor.shared.getElement(account: account) - self.mainNavigationController?.menuToolbar.items?.forEach { - $0.tintColor = color - } + if let userInfo = notification.userInfo, let account = userInfo["account"] as? String { + if let controller = userInfo["controller"] as? NCMainTabBarController, + controller == self.controller { + controller.account = account + } else { + return } + } - self.navigationController?.popToRootViewController(animated: false) - self.serverUrl = self.utilityFileSystem.getHomeServer(session: self.session) - self.isSearchingMode = false - self.isEditMode = false - self.fileSelect.removeAll() - self.layoutForView = self.database.getLayoutForView(account: self.session.account, key: self.layoutKey, serverUrl: self.serverUrl) - - if self.isLayoutList { - self.collectionView?.collectionViewLayout = self.listLayout - } else if self.isLayoutGrid { - self.collectionView?.collectionViewLayout = self.gridLayout - } else if self.isLayoutPhoto { - self.collectionView?.collectionViewLayout = self.mediaLayout - } + self.navigationController?.popToRootViewController(animated: false) + self.serverUrl = self.utilityFileSystem.getHomeServer(session: self.session) + self.isSearchingMode = false + self.isEditMode = false + self.fileSelect.removeAll() + self.layoutForView = self.database.getLayoutForView(account: self.session.account, key: self.layoutKey, serverUrl: self.serverUrl) + + if self.isLayoutList { + self.collectionView?.collectionViewLayout = self.listLayout + } else if self.isLayoutGrid { + self.collectionView?.collectionViewLayout = self.gridLayout + } else if self.isLayoutPhoto { + self.collectionView?.collectionViewLayout = self.mediaLayout + } - self.titleCurrentFolder = self.getNavigationTitle() - self.navigationItem.title = self.titleCurrentFolder + self.titleCurrentFolder = self.getNavigationTitle() + ///Magentacloud branding changes hide user account button on left navigation bar +// self.setNavigationLeftItems() - await (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() + Task { await self.reloadDataSource() await self.getServerData() } @@ -98,6 +122,7 @@ class NCFiles: NCCollectionViewCommon { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + resetPlusButtonAlpha() Task { await self.reloadDataSource() } @@ -124,15 +149,6 @@ class NCFiles: NCCollectionViewCommon { } } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - stopSyncMetadata() - Task { - await NCNetworking.shared.networkingTasks.cancel(identifier: "\(self.serverUrl)_NCFiles") - } - } - override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) @@ -140,6 +156,31 @@ class NCFiles: NCCollectionViewCommon { fileNameOpen = nil } + // MARK: - Action + + @IBAction func plusButtonAction(_ sender: UIButton) { + resetPlusButtonAlpha() + guard let controller else { return } + let fileFolderPath = NCUtilityFileSystem().getFileNamePath("", serverUrl: serverUrl, session: NCSession.shared.getSession(controller: controller)) + let fileFolderName = (serverUrl as NSString).lastPathComponent + let capabilities = NKCapabilities.shared.getCapabilitiesBlocking(for: controller.account) + + if let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", controller.account, serverUrl)) { + if !directory.permissions.contains("CK") { + let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_add_file_") + NCContentPresenter().showWarning(error: error) + return + } + } + + if !FileNameValidator.checkFolderPath(fileFolderPath, account: controller.account, capabilities: capabilities) { + controller.present(UIAlertController.warning(message: "\(String(format: NSLocalizedString("_file_name_validator_error_reserved_name_", comment: ""), fileFolderName)) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) + return + } + + self.appDelegate.toggleMenu(controller: controller, sender: sender) + } + // MARK: - DataSource override func reloadDataSource() async { @@ -148,38 +189,31 @@ class NCFiles: NCCollectionViewCommon { return } + let predicate: NSPredicate = { + if NCKeychain().getPersonalFilesOnly(account: self.session.account) { + return self.personalFilesOnlyPredicate + } else { + return self.defaultPredicate + } + }() + self.metadataFolder = await self.database.getMetadataFolderAsync(session: self.session, serverUrl: self.serverUrl) if let tblDirectory = await self.database.getTableDirectoryAsync(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", self.session.account, self.serverUrl)) { self.richWorkspaceText = tblDirectory.richWorkspace } - if let metadataFolder { - nkLog(info: "Inside metadata folder \(metadataFolder.fileName) with permissions: \(metadataFolder.permissions)") - - // disable + button if no create permission - let color = NCBrandColor.shared.getElement(account: self.session.account) - - if let items = self.mainNavigationController?.menuToolbar.items { - for item in items { - item.isEnabled = metadataFolder.isCreatable - item.tintColor = metadataFolder.isCreatable ? color : .lightGray - } - } - } + let metadatas = await self.database.getMetadatasAsync(predicate: predicate, + withLayout: self.layoutForView, + withAccount: self.session.account) - let metadatas = await self.database.getMetadatasAsyncDataSource(withServerUrl: self.serverUrl, - withUserId: self.session.userId, - withAccount: self.session.account, - withLayout: self.layoutForView) - - self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, - layoutForView: layoutForView, - account: session.account) + self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, layoutForView: layoutForView, account: session.account) await super.reloadDataSource() cachingAsync(metadatas: metadatas) } - override func getServerData(forced: Bool = false) async { + override func getServerData(refresh: Bool = false) async { + await super.getServerData() + defer { stopGUIGetServerData() startSyncMetadata(metadatas: self.dataSource.getMetadatas()) @@ -194,10 +228,7 @@ class NCFiles: NCCollectionViewCommon { } func downloadMetadata(_ metadata: tableMetadata) async -> Bool { - let fileSize = utilityFileSystem.fileProviderStorageSize(metadata.ocId, - fileName: metadata.fileNameView, - userId: metadata.userId, - urlBase: metadata.urlBase) + let fileSize = utilityFileSystem.fileProviderStorageSize(metadata.ocId, fileNameView: metadata.fileNameView) guard fileSize > 0 else { return false } if let tblLocalFile = await database.getTableLocalFileAsync(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) { @@ -208,7 +239,7 @@ class NCFiles: NCCollectionViewCommon { return false } - let resultsReadFolder = await networkReadFolderAsync(serverUrl: self.serverUrl, forced: forced) + let resultsReadFolder = await networkReadFolderAsync(serverUrl: self.serverUrl, refresh: refresh) guard resultsReadFolder.error == .success, resultsReadFolder.reloadRequired else { return } @@ -221,7 +252,7 @@ class NCFiles: NCCollectionViewCommon { session: NCNetworking.shared.sessionDownload, selector: NCGlobal.shared.selectorDownloadFile, sceneIdentifier: self.controller?.sceneIdentifier) { - await NCNetworking.shared.downloadFile(metadata: metadata) + NCNetworking.shared.download(metadata: metadata) } } } @@ -230,40 +261,30 @@ class NCFiles: NCCollectionViewCommon { await self.reloadDataSource() } - private func networkReadFolderAsync(serverUrl: String, forced: Bool) async -> (metadatas: [tableMetadata]?, error: NKError, reloadRequired: Bool) { - var reloadRequired: Bool = false + private func networkReadFolderAsync(serverUrl: String, refresh: Bool) async -> (metadatas: [tableMetadata]?, error: NKError, reloadRequired: Bool) { + let isDirectoryE2EE = await NCUtilityFileSystem().isDirectoryE2EEAsync(session: self.session, serverUrl: serverUrl) let resultsReadFile = await NCNetworking.shared.readFileAsync(serverUrlFileName: serverUrl, account: session.account) { task in - Task { - await NCNetworking.shared.networkingTasks.track(identifier: "\(self.serverUrl)_NCFiles", task: task) - } + self.dataSourceTask = task if self.dataSource.isEmpty() { self.collectionView.reloadData() } } - guard resultsReadFile.error == .success, - let metadata = resultsReadFile.metadata else { - return(nil, resultsReadFile.error, reloadRequired) + guard resultsReadFile.error == .success, let metadata = resultsReadFile.metadata else { + return (nil, resultsReadFile.error, false) } - let e2eEncrypted = metadata.e2eEncrypted - let ocId = metadata.ocId await self.database.updateDirectoryRichWorkspaceAsync(metadata.richWorkspace, account: resultsReadFile.account, serverUrl: serverUrl) let tableDirectory = await self.database.getTableDirectoryAsync(ocId: metadata.ocId) - // Verify LivePhoto - // - reloadRequired = await networking.setLivePhoto(account: resultsReadFile.account) - await NCManageDatabase.shared.deleteLivePhotoError() - let shouldSkipUpdate: Bool = ( - !forced && + !refresh && tableDirectory?.etag == metadata.etag && !metadata.e2eEncrypted && !self.dataSource.isEmpty() ) if shouldSkipUpdate { - return (nil, NKError(), reloadRequired) + return (nil, NKError(), false) } startGUIGetServerData() @@ -272,40 +293,31 @@ class NCFiles: NCCollectionViewCommon { let (account, metadataFolder, metadatas, error) = await NCNetworking.shared.readFolderAsync(serverUrl: serverUrl, account: session.account, options: options) { task in - Task { - await NCNetworking.shared.networkingTasks.track(identifier: "\(self.serverUrl)_NCFiles", task: task) - } + self.dataSourceTask = task if self.dataSource.isEmpty() { self.collectionView.reloadData() } } guard error == .success else { - return(nil, error, reloadRequired) + return (nil, error, false) } - reloadRequired = true if let metadataFolder { self.metadataFolder = metadataFolder.detachedCopy() self.richWorkspaceText = metadataFolder.richWorkspace } - // - // E2EE section - // - - guard e2eEncrypted, - let metadatas, - !metadatas.isEmpty, - NCPreferences().isEndToEndEnabled(account: account), + guard let metadataFolder, + isDirectoryE2EE, + NCKeychain().isEndToEndEnabled(account: account), await !NCNetworkingE2EE().isInUpload(account: account, serverUrl: serverUrl) else { - return(metadatas, error, reloadRequired) + return (metadatas, error, true) } + /// E2EE let lock = await self.database.getE2ETokenLockAsync(account: account, serverUrl: serverUrl) - if let e2eToken = lock?.e2eToken { - nkLog(tag: self.global.logTagE2EE, message: "Tocken: \(e2eToken)", minimumLogLevel: .verbose) - } + let results = await NCNetworkingE2EE().getMetadata(fileId: metadataFolder.ocId, e2eToken: lock?.e2eToken, account: account) let results = await NCNetworkingE2EE().getMetadata(fileId: ocId, e2eToken: lock?.e2eToken, account: account) @@ -366,8 +378,7 @@ class NCFiles: NCCollectionViewCommon { errorCode: error.errorCode) } } - - return (metadatas, error, reloadRequired) + return (metadatas, error, true) } func blinkCell(fileName: String?) { @@ -401,9 +412,39 @@ class NCFiles: NCCollectionViewCommon { } } + override func resetPlusButtonAlpha(animated: Bool = true) { + accumulatedScrollDown = 0 + let update = { + self.plusButton.alpha = 1.0 + } + + if animated { + UIView.animate(withDuration: 0.3, animations: update) + } else { + update() + } + } + + override func isHiddenPlusButton(_ isHidden: Bool) { + if isHidden { + UIView.animate(withDuration: 0.5, delay: 0.0, options: [], animations: { + self.plusButton.transform = CGAffineTransform(translationX: 100, y: 0) + self.plusButton.alpha = 0 + }) + } else { + plusButton.transform = CGAffineTransform(translationX: 100, y: 0) + plusButton.alpha = 0 + + UIView.animate(withDuration: 0.5, delay: 0.3, options: [], animations: { + self.plusButton.transform = .identity + self.plusButton.alpha = 1 + }) + } + } + // MARK: - NCAccountSettingsModelDelegate - override func accountSettingsDidDismiss(tblAccount: tableAccount?, controller: NCMainTabBarController?) { + override func accountSettingsDidDismiss(tableAccount: tableAccount?, controller: NCMainTabBarController?) { let currentAccount = session.account if database.getAllTableAccount().isEmpty { @@ -425,8 +466,6 @@ class NCFiles: NCCollectionViewCommon { navigationItem.title = self.titleCurrentFolder } - Task { - await (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() - } + (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() } } diff --git a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift index b861d54f00..9e969faed9 100644 --- a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift +++ b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift @@ -39,6 +39,8 @@ protocol NCCellProtocol { var fileSharedImage: UIImageView? { get set } var fileMoreImage: UIImageView? { get set } var cellSeparatorView: UIView? { get set } + var indexPath: IndexPath { get set } + var fileSharedLabel: UILabel? { get set } func titleInfoTrailingDefault() func titleInfoTrailingFull() @@ -115,6 +117,10 @@ extension NCCellProtocol { get { return nil } set {} } + var fileSharedLabel: UILabel? { + get { return nil } + set { } + } func titleInfoTrailingDefault() {} func titleInfoTrailingFull() {} diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 8a7f0e3b74..cbf7de1f82 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -37,30 +37,26 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var buttonShared: UIButton! @IBOutlet weak var imageMore: UIImageView! @IBOutlet weak var buttonMore: UIButton! + @IBOutlet weak var progressView: UIProgressView! @IBOutlet weak var separator: UIView! - @IBOutlet weak var tag0: UILabel! - @IBOutlet weak var tag1: UILabel! - + @IBOutlet weak var labelShared: UILabel! @IBOutlet weak var imageItemLeftConstraint: NSLayoutConstraint! @IBOutlet weak var separatorHeightConstraint: NSLayoutConstraint! - @IBOutlet weak var titleTrailingConstraint: NSLayoutConstraint! + @IBOutlet weak var subInfoTrailingConstraint: NSLayoutConstraint! - var ocId = "" - var ocIdTransfer = "" - var user = "" + private var objectId = "" + private var user = "" + var indexPath = IndexPath() weak var listCellDelegate: NCListCellDelegate? + var namedButtonMore = "" var fileAvatarImageView: UIImageView? { return imageShared } - var fileOcId: String? { - get { return ocId } - set { ocId = newValue ?? "" } - } - var fileOcIdTransfer: String? { - get { return ocIdTransfer } - set { ocIdTransfer = newValue ?? "" } + var fileObjectId: String? { + get { return objectId } + set { objectId = newValue ?? "" } } var filePreviewImageView: UIImageView? { get { return imageItem } @@ -82,6 +78,14 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto get { return labelSubinfo } set { labelSubinfo = newValue } } + var fileProgressView: UIProgressView? { + get { return progressView } + set { progressView = newValue } + } + var fileSelectImage: UIImageView? { + get { return imageSelect } + set { imageSelect = newValue } + } var fileStatusImage: UIImageView? { get { return imageStatus } set { imageStatus = newValue } @@ -107,30 +111,18 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto set { separator = newValue } } - override var accessibilityIdentifier: String? { - get { - super.accessibilityIdentifier - } - set { - super.accessibilityIdentifier = newValue - - if let newValue { - buttonShared.accessibilityIdentifier = "\(newValue)/shareButton" - } - } + var fileSharedLabel: UILabel? { + get { return labelShared } + set { labelShared = newValue } } override func awakeFromNib() { super.awakeFromNib() - initCell() - } - override func prepareForReuse() { - super.prepareForReuse() - initCell() - } + imageItem.layer.cornerRadius = 6 + imageItem.layer.masksToBounds = true - func initCell() { + // use entire cell as accessibility element accessibilityHint = nil accessibilityLabel = nil accessibilityValue = nil @@ -157,6 +149,23 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto longPressedGesture.delegate = self longPressedGesture.delaysTouchesBegan = true self.addGestureRecognizer(longPressedGesture) + + separator.backgroundColor = .separator + separatorHeightConstraint.constant = 0.5 + + labelTitle.text = "" + labelInfo.text = "" + labelTitle.textColor = .label + labelInfo.textColor = .systemGray + labelSubinfo.textColor = .systemGray + } + + override func prepareForReuse() { + super.prepareForReuse() + imageItem.backgroundColor = nil + accessibilityHint = nil + accessibilityLabel = nil + accessibilityValue = nil } override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? { @@ -164,40 +173,43 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } @IBAction func touchUpInsideShare(_ sender: Any) { - listCellDelegate?.tapShareListItem(with: ocId, ocIdTransfer: ocIdTransfer, sender: sender) + listCellDelegate?.tapShareListItem(with: objectId, indexPath: indexPath, sender: sender) } @IBAction func touchUpInsideMore(_ sender: Any) { - listCellDelegate?.tapMoreListItem(with: ocId, ocIdTransfer: ocIdTransfer, image: imageItem.image, sender: sender) + listCellDelegate?.tapMoreListItem(with: objectId, namedButtonMore: namedButtonMore, image: imageItem.image, indexPath: indexPath, sender: sender) } @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { - listCellDelegate?.longPressListItem(with: ocId, ocIdTransfer: ocIdTransfer, gestureRecognizer: gestureRecognizer) + listCellDelegate?.longPressListItem(with: objectId, indexPath: indexPath, gestureRecognizer: gestureRecognizer) } fileprivate func setA11yActions() { + let moreName = namedButtonMore == NCGlobal.shared.buttonMoreStop ? "_cancel_" : "_more_" self.accessibilityCustomActions = [ UIAccessibilityCustomAction( name: NSLocalizedString("_share_", comment: ""), target: self, selector: #selector(touchUpInsideShare(_:))), UIAccessibilityCustomAction( - name: NSLocalizedString("_more_", comment: ""), + name: NSLocalizedString(moreName, comment: ""), target: self, selector: #selector(touchUpInsideMore(_:))) ] } func titleInfoTrailingFull() { - titleTrailingConstraint.constant = 10 + subInfoTrailingConstraint.constant = 10 } func titleInfoTrailingDefault() { - titleTrailingConstraint.constant = 90 + subInfoTrailingConstraint.constant = 90 } - func setButtonMore(image: UIImage) { + func setButtonMore(named: String, image: UIImage) { + namedButtonMore = named imageMore.image = image + setA11yActions() } @@ -211,6 +223,10 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto buttonShared.isHidden = status } + func hideSeparator(_ status: Bool) { + separator.isHidden = status + } + func selected(_ status: Bool, isEditMode: Bool) { if isEditMode { imageItemLeftConstraint.constant = 45 @@ -236,11 +252,11 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto blurEffectView?.backgroundColor = .lightGray blurEffectView?.frame = self.bounds blurEffectView?.autoresizingMask = [.flexibleWidth, .flexibleHeight] - imageSelect.image = NCImageCache.shared.getImageCheckedYes() + imageSelect.image = NCImageCache.images.checkedYes backgroundView = blurEffectView separator.isHidden = true } else { - imageSelect.image = NCImageCache.shared.getImageCheckedNo() + imageSelect.image = NCImageCache.images.checkedNo backgroundView = nil separator.isHidden = false } @@ -248,8 +264,8 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } func writeInfoDateSize(date: NSDate, size: Int64) { - labelInfo.text = NCUtility().getRelativeDateTitle(date as Date) - labelSubinfo.text = NCUtilityFileSystem().transformedSize(size) + labelInfo.text = NCUtility().dateDiff(date as Date) + " · " + NCUtilityFileSystem().transformedSize(size) + labelSubinfo.text = "" } func setAccessibility(label: String, value: String) { @@ -322,9 +338,9 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } protocol NCListCellDelegate: AnyObject { - func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) - func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) - func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) + func tapShareListItem(with objectId: String, indexPath: IndexPath, sender: Any) + func tapMoreListItem(with objectId: String, namedButtonMore: String, image: UIImage?, indexPath: IndexPath, sender: Any) + func longPressListItem(with objectId: String, indexPath: IndexPath, gestureRecognizer: UILongPressGestureRecognizer) } // MARK: - List Layout diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.xib b/iOSClient/Main/Collection Common/Cell/NCListCell.xib index 5aea605add..a9b542e428 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.xib @@ -1,258 +1,191 @@ - + - - + + + - - + + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift index e8080bb079..41c4f2385f 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift @@ -14,17 +14,91 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { // get auto upload folder - self.autoUploadFileName = self.database.getAccountAutoUploadFileName(account: session.account) - self.autoUploadDirectory = self.database.getAccountAutoUploadDirectory(account: session.account, urlBase: session.urlBase, userId: session.userId) + self.autoUploadFileName = self.database.getAccountAutoUploadFileName(account: self.session.account) + self.autoUploadDirectory = self.database.getAccountAutoUploadDirectory(session: self.session) // get layout for view - self.layoutForView = self.database.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) - // is a Directory E2EE - if isSearchingMode { - self.isDirectoryE2EE = false + self.layoutForView = self.database.getLayoutForView(account: self.session.account, key: self.layoutKey, serverUrl: self.serverUrl) + + return self.dataSource.numberOfItemsInSection(section) + } + + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + guard let metadata = dataSource.cellForItemAt(indexPath: indexPath), + let cell = (cell as? NCCellProtocol) else { return } + let existsIcon = utilityFileSystem.fileProviderStoragePreviewIconExists(metadata.ocId, etag: metadata.etag) + + func downloadAvatar(fileName: String, user: String, dispalyName: String?) { + if let image = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) { + cell.fileAvatarImageView?.contentMode = .scaleAspectFill + cell.fileAvatarImageView?.image = image + } else { + NCNetworking.shared.downloadAvatar(user: user, dispalyName: dispalyName, fileName: fileName, cell: cell, view: collectionView) + } + } + /// CONTENT MODE + cell.filePreviewImageView?.layer.borderWidth = 0 + if existsIcon { + cell.filePreviewImageView?.contentMode = .scaleAspectFill } else { - self.isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(serverUrl: serverUrl, urlBase: session.urlBase, userId: session.userId, account: session.account) + cell.filePreviewImageView?.contentMode = .scaleAspectFit + } + cell.fileAvatarImageView?.contentMode = .center + /// THUMBNAIL + if !metadata.directory { + if metadata.hasPreviewBorder { + cell.filePreviewImageView?.layer.borderWidth = 0.2 + cell.filePreviewImageView?.layer.borderColor = UIColor.lightGray.cgColor + } + if metadata.name == NCGlobal.shared.appName { + if layoutForView?.layout == NCGlobal.shared.layoutPhotoRatio || layoutForView?.layout == NCGlobal.shared.layoutPhotoSquare { + if let image = NCImageCache.shared.getPreviewImageCache(ocId: metadata.ocId, etag: metadata.etag) { + cell.filePreviewImageView?.image = image + } else if let image = UIImage(contentsOfFile: self.utilityFileSystem.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)) { + cell.filePreviewImageView?.image = image + NCImageCache.shared.addPreviewImageCache(metadata: metadata, image: image) + } + } else { + if let image = NCImageCache.shared.getIconImageCache(ocId: metadata.ocId, etag: metadata.etag) { + cell.filePreviewImageView?.image = image + } else if metadata.hasPreview { + cell.filePreviewImageView?.image = utility.getIcon(metadata: metadata) + } + } + if cell.filePreviewImageView?.image == nil { + if metadata.iconName.isEmpty { + cell.filePreviewImageView?.image = NCImageCache.images.file + } else { + cell.filePreviewImageView?.image = utility.loadImage(named: metadata.iconName, useTypeIconFile: true) + } + if metadata.hasPreview && metadata.status == NCGlobal.shared.metadataStatusNormal && !existsIcon { + for case let operation as NCCollectionViewDownloadThumbnail in NCNetworking.shared.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId { return } + NCNetworking.shared.downloadThumbnailQueue.addOperation(NCCollectionViewDownloadThumbnail(metadata: metadata, cell: cell, collectionView: collectionView)) + } + } + } else { + /// APP NAME - UNIFIED SEARCH + switch metadata.iconName { + case let str where str.contains("contacts"): + cell.filePreviewImageView?.image = NCImageCache.images.iconContacts + case let str where str.contains("conversation"): + cell.filePreviewImageView?.image = NCImageCache.images.iconTalk + case let str where str.contains("calendar"): + cell.filePreviewImageView?.image = NCImageCache.images.iconCalendar + case let str where str.contains("deck"): + cell.filePreviewImageView?.image = NCImageCache.images.iconDeck + case let str where str.contains("mail"): + cell.filePreviewImageView?.image = NCImageCache.images.iconMail + case let str where str.contains("talk"): + cell.filePreviewImageView?.image = NCImageCache.images.iconTalk + case let str where str.contains("confirm"): + cell.filePreviewImageView?.image = NCImageCache.images.iconConfirm + case let str where str.contains("pages"): + cell.filePreviewImageView?.image = NCImageCache.images.iconPages + default: + cell.filePreviewImageView?.image = NCImageCache.images.iconFile + } + } } - return self.dataSource.numberOfItemsInSection(section) } func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { @@ -43,7 +117,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { guard let metadata = self.dataSource.getMetadata(indexPath: indexPath) else { return } - let existsImagePreview = self.utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag, userId: metadata.userId, urlBase: metadata.urlBase) + let existsImagePreview = self.utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag) let ext = self.global.getSizeExtension(column: self.numberOfColumns) if metadata.hasPreview, @@ -62,8 +136,8 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.hideButtonMore(true) cell.hideImageStatus(true) - // Image - // + /// Image + /// if let image = NCImageCache.shared.getImageCache(ocId: metadata.ocId, etag: metadata.etag, ext: ext) { cell.filePreviewImageView?.image = image @@ -72,11 +146,11 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } else { if isPinchGestureActive || ext == global.previewExt512 || ext == global.previewExt1024 { - cell.filePreviewImageView?.image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext, userId: metadata.userId, urlBase: metadata.urlBase) + cell.filePreviewImageView?.image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext) } DispatchQueue.global(qos: .userInteractive).async { - let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext, userId: metadata.userId, urlBase: metadata.urlBase) + let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext) if let image { self.imageCache.addImageCache(ocId: metadata.ocId, etag: metadata.etag, image: image, ext: ext, cost: indexPath.row) DispatchQueue.main.async { @@ -96,8 +170,15 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } } - // Edit mode - // + /// Status + /// + if metadata.isLivePhoto { + cell.fileStatusImage?.image = utility.loadImage(named: "livephoto", colors: isLayoutPhoto ? [.white] : [NCBrandColor.shared.iconImageColor2]) + } else if metadata.isVideo { + cell.fileStatusImage?.image = utility.loadImage(named: "play.circle", colors: NCBrandColor.shared.iconImageMultiColors) + } + + /// Edit mode if fileSelect.contains(metadata.ocId) { cell.selected(true, isEditMode: isEditMode) } else { @@ -114,24 +195,40 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { var cell: NCCellProtocol & UICollectionViewCell + let permissions = NCPermissions() var isShare = false var isMounted = false var a11yValues: [String] = [] + + // LAYOUT PHOTO + if layoutForView?.layout == NCGlobal.shared.layoutPhotoRatio || layoutForView?.layout == NCGlobal.shared.layoutPhotoSquare { + guard let photoCell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as? NCPhotoCell else { return NCPhotoCell() } + photoCell.photoCellDelegate = self + cell = photoCell + } else if layoutForView?.layout == NCGlobal.shared.layoutGrid { + // LAYOUT GRID + guard let gridCell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridCell else { return NCGridCell() } + gridCell.gridCellDelegate = self + cell = gridCell + } else { + // LAYOUT LIST + guard let listCell = collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as? NCListCell else { return NCListCell() } + listCell.listCellDelegate = self + cell = listCell + } let metadata = self.dataSource.getMetadata(indexPath: indexPath) ?? tableMetadata() - let existsImagePreview = utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag, userId: metadata.userId, urlBase: metadata.urlBase) - let ext = global.getSizeExtension(column: self.numberOfColumns) + let shares = NCManageDatabase.shared.getTableShares(metadata: metadata) defer { - let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() if !metadata.isSharable() || (!capabilities.fileSharingApiEnabled && !capabilities.filesComments && capabilities.activity.isEmpty) { cell.hideButtonShare(true) } } // E2EE create preview - if self.isDirectoryE2EE, + if self.isDirectoryEncrypted, metadata.isImageOrVideo, - !utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag, userId: metadata.userId, urlBase: metadata.urlBase) { + !utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag) { utility.createImageFileFrom(metadata: metadata) } @@ -159,7 +256,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell = listCell } - // CONTENT MODE + /// CONTENT MODE cell.fileAvatarImageView?.contentMode = .center cell.filePreviewImageView?.layer.borderWidth = 0 @@ -174,8 +271,8 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } if metadataFolder != nil { - isShare = metadata.permissions.contains(NCMetadataPermissions.permissionShared) && !metadataFolder!.permissions.contains(NCMetadataPermissions.permissionShared) - isMounted = metadata.permissions.contains(NCMetadataPermissions.permissionMounted) && !metadataFolder!.permissions.contains(NCMetadataPermissions.permissionMounted) + isShare = metadata.permissions.contains(permissions.permissionShared) && !metadataFolder!.permissions.contains(permissions.permissionShared) + isMounted = metadata.permissions.contains(permissions.permissionMounted) && !metadataFolder!.permissions.contains(permissions.permissionMounted) } cell.fileAccount = metadata.account @@ -184,6 +281,8 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.fileUser = metadata.ownerId if isSearchingMode { + cell.fileTitleLabel?.text = metadata.fileName + cell.fileTitleLabel?.lineBreakMode = .byTruncatingTail if metadata.name == global.appName { cell.fileInfoLabel?.text = NSLocalizedString("_in_", comment: "") + " " + utilityFileSystem.getPath(path: metadata.path, user: metadata.user) } else { @@ -191,23 +290,16 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } cell.fileSubinfoLabel?.isHidden = true } else if !metadata.sessionError.isEmpty, metadata.status != global.metadataStatusNormal { + cell.fileTitleLabel?.text = metadata.fileNameView cell.fileSubinfoLabel?.isHidden = false cell.fileInfoLabel?.text = metadata.sessionError } else { cell.fileSubinfoLabel?.isHidden = false - + cell.fileTitleLabel?.text = metadata.fileNameView + cell.fileTitleLabel?.lineBreakMode = .byTruncatingMiddle cell.writeInfoDateSize(date: metadata.date, size: metadata.size) } -// if cell is NCListCell && cell.fileTitleLabel is BidiFilenameLabel { -// (cell.fileTitleLabel as? BidiFilenameLabel)?.fullFilename = metadata.fileNameView -// (cell.fileTitleLabel as? BidiFilenameLabel)?.isFolder = metadata.directory -// (cell.fileTitleLabel as? BidiFilenameLabel)?.numberOfLines = 1 -// -// } else { - cell.fileTitleLabel?.text = metadata.fileNameView -// } - // Accessibility [shared] if metadata.ownerId != appDelegate.userId, appDelegate.account == metadata.account { if metadata.ownerId != metadata.userId { a11yValues.append(NSLocalizedString("_shared_with_you_by_", comment: "") + " " + metadata.ownerDisplayName) @@ -255,7 +347,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if metadata.name == global.appName { if let image = NCImageCache.shared.getImageCache(ocId: metadata.ocId, etag: metadata.etag, ext: ext) { cell.filePreviewImageView?.image = image - } else if let image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext, userId: metadata.userId, urlBase: metadata.urlBase) { + } else if let image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext) { cell.filePreviewImageView?.image = image } @@ -267,7 +359,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } } } else { - // APP NAME - UNIFIED SEARCH + /// APP NAME - UNIFIED SEARCH switch metadata.iconName { case let str where str.contains("contacts"): cell.filePreviewImageView?.image = utility.loadImage(named: "person.crop.rectangle.stack", colors: [NCBrandColor.shared.iconImageColor]) @@ -291,21 +383,16 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if !metadata.iconUrl.isEmpty { if let ownerId = getAvatarFromIconUrl(metadata: metadata) { let fileName = NCSession.shared.getFileName(urlBase: metadata.urlBase, user: ownerId) - if let image = NCImageCache.shared.getImageCache(key: fileName) { - cell.filePreviewImageView?.image = image - } else { - self.database.getImageAvatarLoaded(fileName: fileName) { image, tblAvatar in - if let image { - cell.filePreviewImageView?.image = image - NCImageCache.shared.addImageCache(image: image, key: fileName) - } else { - cell.filePreviewImageView?.image = self.utility.loadUserImage(for: ownerId, displayName: nil, urlBase: metadata.urlBase) - } - - if !(tblAvatar?.loaded ?? false), - self.networking.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty { - self.networking.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: ownerId, fileName: fileName, account: metadata.account, view: collectionView, isPreviewImageView: true)) - } + self.database.getImageAvatarLoaded(fileName: fileName) { image, tblAvatar in + if let image { + cell.filePreviewImageView?.image = image + } else { + cell.filePreviewImageView?.image = self.utility.loadUserImage(for: ownerId, displayName: nil, urlBase: metadata.urlBase) + } + + if !(tblAvatar?.loaded ?? false), + self.networking.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty { + self.networking.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: ownerId, fileName: fileName, account: metadata.account, view: collectionView, isPreviewImageView: true)) } } } @@ -334,7 +421,25 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { (cell.fileSharedImage?.image = imageCache.getImageShareByLink()) : (cell.fileSharedImage?.image = imageCache.getImageShared()) } else { - cell.fileSharedImage?.image = imageCache.getImageCanShare() + cell.fileSharedImage?.image = NCImageCache.images.canShare.image(color: NCBrandColor.shared.gray60, size: 50) + cell.fileSharedLabel?.text = "" + } + if appDelegate.account != metadata.account { + cell.fileSharedImage?.image = NCImageCache.images.shared + } + cell.fileSharedLabel?.text = NSLocalizedString("_shared_", comment: "") + cell.fileSharedLabel?.textColor = NCBrandColor.shared.customer + if (!metadata.shareType.isEmpty || !(shares.share?.isEmpty ?? true) || (shares.firstShareLink != nil)){ + cell.fileSharedImage?.image = cell.fileSharedImage?.image?.imageColor(NCBrandColor.shared.customer) + } else { + cell.fileSharedImage?.image = NCImageCache.images.canShare.image(color: NCBrandColor.shared.gray60, size: 50) + cell.fileSharedLabel?.text = "" + } + + if metadata.permissions.contains("S"), (metadata.permissions.range(of: "S") != nil) { + cell.fileSharedImage?.image = NCImageCache.images.sharedWithMe + cell.fileSharedLabel?.text = NSLocalizedString("_recieved_", comment: "") + cell.fileSharedLabel?.textColor = NCBrandColor.shared.notificationAction } // Button More @@ -372,7 +477,9 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { case global.metadataStatusWaitDownload: cell.fileStatusImage?.image = utility.loadImage(named: "arrow.triangle.2.circlepath", colors: NCBrandColor.shared.iconImageMultiColors) case global.metadataStatusDownloading: - cell.fileStatusImage?.image = utility.loadImage(named: "arrowshape.down.circle", colors: NCBrandColor.shared.iconImageMultiColors) + if #available(iOS 17.0, *) { + cell.fileStatusImage?.image = utility.loadImage(named: "arrowshape.down.circle", colors: NCBrandColor.shared.iconImageMultiColors) + } case global.metadataStatusDownloadError, global.metadataStatusUploadError: cell.fileStatusImage?.image = utility.loadImage(named: "exclamationmark.circle", colors: NCBrandColor.shared.iconImageMultiColors) default: @@ -381,25 +488,19 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { // AVATAR if !metadata.ownerId.isEmpty, metadata.ownerId != metadata.userId { + cell.fileAvatarImageView?.contentMode = .scaleAspectFill + let fileName = NCSession.shared.getFileName(urlBase: metadata.urlBase, user: metadata.ownerId) - if let image = NCImageCache.shared.getImageCache(key: fileName) { - cell.fileAvatarImageView?.contentMode = .scaleAspectFill - cell.fileAvatarImageView?.image = image - } else { - self.database.getImageAvatarLoaded(fileName: fileName) { image, tblAvatar in - if let image { - cell.fileAvatarImageView?.contentMode = .scaleAspectFill - cell.fileAvatarImageView?.image = image - NCImageCache.shared.addImageCache(image: image, key: fileName) - } else { - cell.fileAvatarImageView?.contentMode = .scaleAspectFill - cell.fileAvatarImageView?.image = self.utility.loadUserImage(for: metadata.ownerId, displayName: metadata.ownerDisplayName, urlBase: metadata.urlBase) - } + self.database.getImageAvatarLoaded(fileName: fileName) { image, tblAvatar in + if let image { + cell.fileAvatarImageView?.image = image + } else { + cell.fileAvatarImageView?.image = self.utility.loadUserImage(for: metadata.ownerId, displayName: metadata.ownerDisplayName, urlBase: metadata.urlBase) + } - if !(tblAvatar?.loaded ?? false), - self.networking.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty { - self.networking.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: metadata.ownerId, fileName: fileName, account: metadata.account, view: collectionView)) - } + if !(tblAvatar?.loaded ?? false), + self.networking.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty { + self.networking.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: metadata.ownerId, fileName: fileName, account: metadata.account, view: collectionView)) } } } @@ -482,6 +583,14 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.setIconOutlines() + // Hide lines on iPhone + if !UIDevice.current.orientation.isLandscape && UIDevice.current.model.hasPrefix("iPhone") { + cell.cellSeparatorView?.isHidden = true + cell.fileSharedLabel?.isHidden = true + }else{ + cell.cellSeparatorView?.isHidden = false + cell.fileSharedLabel?.isHidden = false + } return cell } @@ -489,11 +598,20 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { func setContent(header: UICollectionReusableView, indexPath: IndexPath) { let (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) = getHeaderHeight(section: indexPath.section) + if kind == UICollectionView.elementKindSectionHeader { + + if dataSource.getMetadataSourceForAllSections().isEmpty { + + guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionFirstHeaderEmptyData", for: indexPath) as? NCSectionFirstHeaderEmptyData else { return NCSectionFirstHeaderEmptyData() } + self.sectionFirstHeaderEmptyData = header + header.delegate = self + + } if let header = header as? NCSectionFirstHeader { let recommendations = self.database.getRecommendedFiles(account: self.session.account) var sectionText = NSLocalizedString("_all_files_", comment: "") - if NCPreferences().getPersonalFilesOnly(account: session.account) { + if NCKeychain().getPersonalFilesOnly(account: session.account) { sectionText = NSLocalizedString("_personal_files_", comment: "") } @@ -517,7 +635,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if isSearchingMode { emptyImage = utility.loadImage(named: "magnifyingglass", colors: [NCBrandColor.shared.getElement(account: session.account)]) - if self.searchDataSourceTask?.state == .running { + if self.dataSourceTask?.state == .running { emptyTitle = NSLocalizedString("_search_in_progress_", comment: "") } else { emptyTitle = NSLocalizedString("_search_no_record_found_", comment: "") @@ -540,10 +658,6 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { emptyImage = utility.loadImage(named: "arrow.triangle.2.circlepath", colors: [NCBrandColor.shared.getElement(account: session.account)]) emptyTitle = NSLocalizedString("_files_no_files_", comment: "") emptyDescription = NSLocalizedString("_folder_offline_desc_", comment: "") - } else if let metadataFolder, !metadataFolder.isCreatable { - emptyImage = imageCache.getFolder(account: session.account) - emptyTitle = NSLocalizedString("_files_no_files_", comment: "") - emptyDescription = NSLocalizedString("_no_file_no_permission_to_create_", comment: "") } else { emptyImage = imageCache.getFolder(account: session.account) emptyTitle = NSLocalizedString("_files_no_files_", comment: "") @@ -576,7 +690,40 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionFirstHeader", for: indexPath) as? NCSectionFirstHeader else { return NCSectionFirstHeader() } self.sectionFirstHeader = header - setContent(header: header, indexPath: indexPath) +// setContent(header: header, indexPath: indexPath) + if layoutForView?.layout == NCGlobal.shared.layoutGrid { + header.setImageSwitchList() + header.buttonSwitch.accessibilityLabel = NSLocalizedString("_list_view_", comment: "") + } else { + header.setImageSwitchGrid() + header.buttonSwitch.accessibilityLabel = NSLocalizedString("_grid_view_", comment: "") + } + header.delegate = self + + if !isSearchingMode, headerMenuTransferView, isHeaderMenuTransferViewEnabled() != nil { + header.setViewTransfer(isHidden: false) + } else { + header.setViewTransfer(isHidden: true) + } + + if headerMenuButtonsView { + header.setStatusButtonsView(enable: !dataSource.getMetadataSourceForAllSections().isEmpty) + header.setButtonsView(height: NCGlobal.shared.heightButtonsView) + header.setSortedTitle(layoutForView?.titleButtonHeader ?? "") + } else { + header.setButtonsView(height: 0) + } + + header.setRichWorkspaceHeight(heightHeaderRichWorkspace) + header.setRichWorkspaceText(richWorkspaceText) + + header.setSectionHeight(heightHeaderSection) + if heightHeaderSection == 0 { + header.labelSection.text = "" + } else { + header.labelSection.text = self.dataSource.getSectionValueLocalization(indexPath: indexPath) + } + header.labelSection.textColor = NCBrandColor.shared.textColor return header @@ -601,10 +748,15 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { footer.setTitleLabel("") footer.setButtonText(NSLocalizedString("_show_more_results_", comment: "")) + footer.separatorIsHidden(true) footer.buttonIsHidden(true) footer.hideActivityIndicatorSection() if isSearchingMode { + if sections > 1 && section != sections - 1 { + footer.separatorIsHidden(false) + } + // If the number of entries(metadatas) is lower than the cursor, then there are no more entries. // 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. // This is as good as it gets for determining last page without server-side flag. @@ -617,15 +769,12 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if unifiedSearchInProgress { footer.showActivityIndicatorSection() } - } else if isEditMode { - // let itemsSelected = self.fileSelect.count - // let items = self.dataSource.numberOfItemsInSection(section) - // footer.setTitleLabel("\(itemsSelected) \(NSLocalizedString("_of_", comment: "")) \(items) \(NSLocalizedString("_selected_", comment: ""))") - footer.setTitleLabel("") } else { if sections == 1 || section == sections - 1 { let info = self.dataSource.getFooterInformation() footer.setTitleLabel(directories: info.directories, files: info.files, size: info.size) + } else { + footer.separatorIsHidden(false) } } return footer @@ -670,7 +819,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { // caching preview // - if let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: self.global.previewExt256, userId: metadata.userId, urlBase: metadata.urlBase) { + if let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: self.global.previewExt256) { NCImageCache.shared.addImageCache(ocId: metadata.ocId, etag: metadata.etag, image: image, ext: self.global.previewExt256, cost: cost) } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift index 9f37ebd5cb..141ef55c3b 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCCollectionViewCommon+SelectTabBar.swift +// Nextcloud +// +// Created by Milen on 01.03.24. +// Copyright © 2024 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// import UIKit import Foundation @@ -131,10 +150,146 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { if editMode { navigationItem.leftBarButtonItems = nil } else { - await (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() + ///Magentacloud branding changes hide user account button on left navigation bar +// setNavigationLeftItems() + } + (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() + + self.collectionView.reloadData() + } + + func convertLivePhoto(metadataFirst: tableMetadata?, metadataLast: tableMetadata?) { + if let metadataFirst, let metadataLast { + Task { + await self.networking.setLivePhoto(metadataFirst: metadataFirst, metadataLast: metadataLast) + } + } + setEditMode(false) + } + + /// If explicit `isOn` is not set, it will invert `isEditMode` + func toggleSelect(isOn: Bool? = nil) { + DispatchQueue.main.async { + self.isEditMode = isOn ?? !self.isEditMode + self.selectOcId.removeAll() + self.setNavigationLeftItems() + self.setNavigationRightItems() + self.collectionView.reloadData() } await (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() self.collectionView.reloadData() } + + func createMenuActions() -> [NCMenuAction] { + var actions = [NCMenuAction]() + + actions.append(.cancelAction { + self.toggleSelect() + }) + if selectOcId.count != dataSource.getMetadataSourceForAllSections().count { + actions.append(.selectAllAction(action: selectAll)) + } + + guard !selectOcId.isEmpty else { return actions } + + actions.append(.seperator(order: 0)) + + var selectedMetadatas: [tableMetadata] = [] + var selectedMediaMetadatas: [tableMetadata] = [] + var isAnyOffline = false + var isAnyFolder = false + var isAnyLocked = false + var canUnlock = true + var canOpenIn = false + var isDirectoryE2EE = false + + for ocId in selectOcId { + guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { continue } + if metadata.e2eEncrypted { + selectOcId.removeAll(where: {$0 == metadata.ocId}) + } else { + selectedMetadatas.append(metadata) + } + + if [NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue].contains(metadata.classFile) { + selectedMediaMetadatas.append(metadata) + } + if metadata.directory { isAnyFolder = true } + if metadata.lock { + isAnyLocked = true + if metadata.lockOwner != appDelegate.userId { + canUnlock = false + } + } + + guard !isAnyOffline else { continue } + if metadata.directory, + let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", appDelegate.account, metadata.serverUrl + "/" + metadata.fileName)) { + isAnyOffline = directory.offline + } else if let localFile = NCManageDatabase.shared.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) { + isAnyOffline = localFile.offline + } // else: file is not offline, continue + + if !metadata.directory { + canOpenIn = true + } + + if metadata.isDirectoryE2EE { + isDirectoryE2EE = true + } + } + + if canOpenIn { + actions.append(.share(selectedMetadatas: selectedMetadatas, viewController: self, completion: { self.toggleSelect() })) + } + + if !isAnyFolder, canUnlock, !NCGlobal.shared.capabilityFilesLockVersion.isEmpty { + actions.append(.lockUnlockFiles(shouldLock: !isAnyLocked, metadatas: selectedMetadatas, completion: { self.toggleSelect() })) + } + + if !selectedMediaMetadatas.isEmpty { + var title: String = NSLocalizedString("_save_selected_files_", comment: "") + var icon = NCUtility().loadImage(named: "save_files",colors: [NCBrandColor.shared.iconImageColor]) + if selectedMediaMetadatas.allSatisfy({ NCManageDatabase.shared.getMetadataLivePhoto(metadata: $0) != nil }) { + title = NSLocalizedString("_livephoto_save_", comment: "") + icon = NCUtility().loadImage(named: "livephoto") + } + + actions.append(NCMenuAction( + title: title, + icon: icon, + order: 0, + action: { _ in + for metadata in selectedMediaMetadatas { + if let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) { + NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV, hudView: self.view)) + } else { + if NCUtilityFileSystem().fileProviderStorageExists(metadata) { + NCActionCenter.shared.saveAlbum(metadata: metadata, controller: self.tabBarController as? NCMainTabBarController) + } else { + if NCNetworking.shared.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty { + NCNetworking.shared.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum)) + } + } + } + } + self.toggleSelect() + } + ) + ) + } + actions.append(.setAvailableOfflineAction(selectedMetadatas: selectedMetadatas, isAnyOffline: isAnyOffline, viewController: self, completion: { + self.reloadDataSource() + self.toggleSelect() + })) + + if !isDirectoryE2EE { + actions.append(.moveOrCopyAction(selectedMetadatas: selectedMetadatas, viewController: self, indexPath: [], completion: { self.toggleSelect() })) + actions.append(.copyAction(selectOcId: selectOcId, viewController: self, completion: { self.toggleSelect() })) + } + actions.append(.deleteAction(selectedMetadatas: selectedMetadatas, indexPaths: [], viewController: self, completion: { self.toggleSelect() })) + return actions + } + } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift new file mode 100644 index 0000000000..11a580f6a3 --- /dev/null +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift @@ -0,0 +1,94 @@ +// +// NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift +// Nextcloud +// +// Created by Milen on 01.03.24. +// Copyright © 2024 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +import Foundation +import SwipeCellKit + +extension NCCollectionViewCommon: SwipeCollectionViewCellDelegate { + func collectionView(_ collectionView: UICollectionView, editActionsForItemAt indexPath: IndexPath, for orientation: SwipeCellKit.SwipeActionsOrientation) -> [SwipeCellKit.SwipeAction]? { + guard orientation == .right, let metadata = self.dataSource.cellForItemAt(indexPath: indexPath) else { return nil } + + let scaleTransition = ScaleTransition(duration: 0.3, initialScale: 0.8, threshold: 0.8) + + // wait a fix for truncate the text .. ? .. + let favoriteAction = SwipeAction(style: .default, title: NSLocalizedString(metadata.favorite ? "_favorite_short_" : "_favorite_short_", comment: "") ) { _, _ in + NCNetworking.shared.favoriteMetadata(metadata) { error in + if error != .success { + NCContentPresenter().showError(error: error) + } + } + } + favoriteAction.backgroundColor = NCBrandColor.shared.yellowFavorite + favoriteAction.image = .init(systemName: "star.fill") + favoriteAction.transitionDelegate = scaleTransition + favoriteAction.hidesWhenSelected = true + + var actions = [favoriteAction] + + let shareAction = SwipeAction(style: .default, title: NSLocalizedString("_share_", comment: "")) { _, _ in + NCActionCenter.shared.openActivityViewController(selectedMetadata: [metadata]) + } + shareAction.backgroundColor = .blue + shareAction.image = .init(systemName: "square.and.arrow.up") + shareAction.transitionDelegate = scaleTransition + shareAction.hidesWhenSelected = true + + let deleteAction = SwipeAction(style: .destructive, title: NSLocalizedString("_delete_", comment: "")) { _, _ in + let titleDelete: String + + if metadata.directory { + titleDelete = NSLocalizedString("_delete_folder_", comment: "") + } else { + titleDelete = NSLocalizedString("_delete_file_", comment: "") + } + + let message = NSLocalizedString("_want_delete_", comment: "") + "\n - " + metadata.fileNameView + + let alertController = UIAlertController.deleteFileOrFolder(titleString: titleDelete + "?", message: message, canDeleteServer: !metadata.lock, selectedMetadatas: [metadata], indexPaths: self.selectIndexPaths) { _ in } + + self.viewController.present(alertController, animated: true, completion: nil) + } + deleteAction.image = .init(systemName: "trash") + deleteAction.style = .destructive + deleteAction.transitionDelegate = scaleTransition + deleteAction.hidesWhenSelected = true + + if !NCManageDatabase.shared.isMetadataShareOrMounted(metadata: metadata, metadataFolder: metadataFolder) { + actions.insert(deleteAction, at: 0) + } + + if metadata.canShare { + actions.append(shareAction) + } + + return actions + } + + func collectionView(_ collectionView: UICollectionView, editActionsOptionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions { + var options = SwipeOptions() + options.expansionStyle = .selection + options.transitionStyle = .border + options.backgroundColor = .clear + return options + } +} diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index ad09d5b6f2..9bf8db55a3 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2020 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCCollectionViewCommon.swift +// Nextcloud +// +// Created by Marino Faggiana on 12/09/2020. +// Copyright © 2020 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// import UIKit import SwiftUI @@ -30,7 +49,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var backgroundImageView = UIImageView() var serverUrl: String = "" var isEditMode = false - var isDirectoryE2EE = false + var isDirectoryEncrypted = false var fileSelect: [String] = [] var metadataFolder: tableMetadata? var richWorkspaceText: String? @@ -39,7 +58,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var isSearchingMode: Bool = false var networkSearchInProgress: Bool = false var layoutForView: NCDBLayoutForView? - var searchDataSourceTask: URLSessionTask? + var dataSourceTask: URLSessionTask? var providers: [NKSearchProvider]? var searchResults: [NKSearchResult]? var listLayout = NCListLayout() @@ -50,6 +69,9 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var tabBarSelect: NCCollectionViewCommonSelectTabBar? var attributesZoomIn: UIMenuElement.Attributes = [] var attributesZoomOut: UIMenuElement.Attributes = [] + let maxImageGrid: CGFloat = 7 + var headerMenu: NCSectionFirstHeader? + var tipViewAccounts: EasyTipView? var syncMetadatasTask: Task? @@ -62,6 +84,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var emptyImageName: String? var emptyImageColors: [UIColor]? + var headerMenuButtonsView: Bool = true + var emptyImage: UIImage? var emptyTitle: String = "" var emptyDescription: String = "" @@ -83,7 +107,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let heightHeaderRecommendations: CGFloat = 160 let heightHeaderSection: CGFloat = 30 - @MainActor var session: NCSession.Session { NCSession.shared.getSession(controller: tabBarController) } @@ -101,31 +124,36 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } var showDescription: Bool { - !headerRichWorkspaceDisable && NCPreferences().showDescription + !headerRichWorkspaceDisable && NCKeychain().showDescription } var isRecommendationActived: Bool { - let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() - return self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session) && capabilities.recommendations + self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session) && + capabilities.recommendations } var infoLabelsSeparator: String { layoutForView?.layout == global.layoutList ? " - " : "" } - @MainActor var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController } - var mainNavigationController: NCMainNavigationController? { - self.navigationController as? NCMainNavigationController - } - var sceneIdentifier: String { (self.tabBarController as? NCMainTabBarController)?.sceneIdentifier ?? "" } + var defaultPredicate: NSPredicate { + let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, self.global.metadataStatusHideInView, NKTypeClassFile.video.rawValue) + return predicate + } + + var personalFilesOnlyPredicate: NSPredicate { + let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND (ownerId == %@ || ownerId == '') AND mountType == '' AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, session.userId, global.metadataStatusHideInView, NKTypeClassFile.video.rawValue) + return predicate + } + var isNumberOfItemsInAllSectionsNull: Bool { var totalItems = 0 for section in 0.. Bool { - let capabilities = await NKCapabilities.shared.getCapabilities(for: session.account) - return self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session) && capabilities.recommendations + var capabilities: NKCapabilities.Capabilities { + NKCapabilities.shared.getCapabilitiesBlocking(for: session.account) } internal let debouncer = NCDebouncer(maxEventCount: NCBrandOptions.shared.numMaximumProcess) @@ -158,6 +185,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewDidLoad() { super.viewDidLoad() + tabBarSelect = NCCollectionViewCommonSelectTabBar(controller: self.controller, delegate: self) self.navigationController?.presentationController?.delegate = self collectionView.alwaysBounceVertical = true collectionView.accessibilityIdentifier = "NCCollectionViewCommon" @@ -197,16 +225,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS collectionView.refreshControl = refreshControl refreshControl.action(for: .valueChanged) { _ in - Task { @MainActor in - // Perform async server forced - await self.getServerData(forced: true) - - // Stop the refresh control after data is loaded - self.refreshControl.endRefreshing() - - // Wait 1.5 seconds before resetting the button alpha - try? await Task.sleep(nanoseconds: 1_500_000_000) - self.mainNavigationController?.resetPlusButtonAlpha() + Task { + await self.getServerData(refresh: true) + } + self.refreshControl.endRefreshing() + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { + self.resetPlusButtonAlpha() } } @@ -227,12 +251,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let dropInteraction = UIDropInteraction(delegate: self) self.navigationController?.navigationItem.leftBarButtonItems?.first?.customView?.addInteraction(dropInteraction) - registerForTraitChanges([UITraitUserInterfaceStyle.self]) { [weak self] (view: NCCollectionViewCommon, _) in - guard let self else { return } - - self.sectionFirstHeader?.setRichWorkspaceColor(style: view.traitCollection.userInterfaceStyle) - } - NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterChangeTheming), object: nil, queue: .main) { [weak self] _ in guard let self else { return } self.collectionView.reloadData() @@ -251,16 +269,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } navigationItem.title = titleCurrentFolder - if tabBarSelect == nil { - tabBarSelect = NCCollectionViewCommonSelectTabBar(controller: self.controller, viewController: self, delegate: self) - } - isEditMode = false - Task { - await (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() - await (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() - } + /// Magentacloud branding changes hide user account button on left navigation bar +// setNavigationLeftItems() + setNavigationRightItems() layoutForView = database.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) if isLayoutList { @@ -283,9 +296,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - Task { - await NCNetworking.shared.transferDispatcher.addDelegate(self) - } + self.networking.addDelegate(self) NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(closeRichWorkspaceWebView), name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) @@ -300,15 +311,13 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // Cancel Queue & Retrieves Properties self.networking.downloadThumbnailQueue.cancelAll() self.networking.unifiedSearchQueue.cancelAll() - searchDataSourceTask?.cancel() + dataSourceTask?.cancel() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - Task { - await NCNetworking.shared.transferDispatcher.removeDelegate(self) - } + self.networking.removeDelegate(self) NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) @@ -341,6 +350,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS return true } + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + + tabBarSelect?.setFrame() + } + // MARK: - Transfer Delegate func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } @@ -448,7 +463,23 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - NotificationCenter @objc func applicationWillResignActive(_ notification: NSNotification) { - mainNavigationController?.resetPlusButtonAlpha() + self.resetPlusButtonAlpha() + refreshControlEndRefreshing() + } + + @objc func reloadAvatar(_ notification: NSNotification) { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.showTip() + } + guard let userInfo = notification.userInfo as NSDictionary?, + let error = userInfo["error"] as? NKError, + error.errorCode != global.errorNotModified else { return } + /// Magentacloud branding changes hide user account button on left navigation bar +// setNavigationLeftItems() + } + + @objc func changeTheming(_ notification: NSNotification) { + self.reloadDataSource() } @objc func closeRichWorkspaceWebView() { @@ -460,68 +491,581 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - Layout func changeLayout(layoutForView: NCDBLayoutForView) { - let homeServer = utilityFileSystem.getHomeServer(urlBase: session.urlBase, userId: session.userId) - let numFoldersLayoutsForView = self.database.getLayoutsForView(keyStore: layoutForView.keyStore)?.count ?? 1 + if self.layoutForView?.layout == layoutForView.layout { + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) + Task { + await self.reloadDataSource() + } + return + } - func changeLayout(withSubFolders: Bool) { - if self.layoutForView?.layout == layoutForView.layout { - self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) - Task { - await self.reloadDataSource() + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) + layoutForView.layout = layoutForView.layout + self.layoutType = layoutForView.layout + + collectionView.reloadData() + + switch layoutForView.layout { + case global.layoutList: + self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) + case global.layoutGrid: + self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) + case global.layoutPhotoSquare, global.layoutPhotoRatio: + self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) + default: + break + } + + self.collectionView.collectionViewLayout.invalidateLayout() + + (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + } + + @objc func reloadDataSource(_ notification: NSNotification) { + if let userInfo = notification.userInfo as? NSDictionary { + if let serverUrl = userInfo["serverUrl"] as? String { + if serverUrl != self.serverUrl { + return } + } + + if let clearDataSource = userInfo["clearDataSource"] as? Bool, clearDataSource { + self.dataSource.removeAll() + } + } + + reloadDataSource() + } + + @objc func getServerData(_ notification: NSNotification) { + if let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String { + if serverUrl != self.serverUrl { return } + } - self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) - layoutForView.layout = layoutForView.layout - self.layoutType = layoutForView.layout + getServerData() + } - collectionView.reloadData() + @objc func reloadHeader(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + account == session.account + else { return } - switch layoutForView.layout { - case global.layoutList: - self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) - case global.layoutGrid: - self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) - case global.layoutPhotoSquare, global.layoutPhotoRatio: - self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) - default: - break + self.collectionView.reloadData() + } + + @objc func changeStatusFolderE2EE(_ notification: NSNotification) { + reloadDataSource() + } + + @objc func closeRichWorkspaceWebView() { + reloadDataSource() + } + + @objc func deleteFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let error = userInfo["error"] as? NKError else { return } + + if error == .success { + if isSearchingMode { + return networkSearch() } - self.collectionView.collectionViewLayout.invalidateLayout() + if isRecommendationActived { + Task.detached { + await NCNetworking.shared.createRecommendations(session: self.session) + } + } + } else { + NCContentPresenter().showError(error: error) + } - Task { - await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + reloadDataSource() + } + + @objc func copyMoveFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String, + account == session.account else { return } + + if isSearchingMode { + return networkSearch() + } + + if isRecommendationActived { + Task.detached { + await NCNetworking.shared.createRecommendations(session: self.session) + } + } + + if serverUrl == self.serverUrl { + reloadDataSource() + } + } + + @objc func renameFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + let serverUrl = userInfo["serverUrl"] as? String, + let error = userInfo["error"] as? NKError, + account == session.account + else { return } + + if error == .success { + if isSearchingMode { + return networkSearch() + } + + if isRecommendationActived { + Task.detached { + await NCNetworking.shared.createRecommendations(session: self.session) + } + } + } + + if serverUrl == self.serverUrl { + if error != .success { + NCContentPresenter().showError(error: error) + } + reloadDataSource() + } + } + + @objc func createFolder(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let ocId = userInfo["ocId"] as? String, + let account = userInfo["account"] as? String, + account == session.account, + let withPush = userInfo["withPush"] as? Bool, + let metadata = database.getMetadataFromOcId(ocId) + else { return } + + if isSearchingMode { + return networkSearch() + } + + if metadata.serverUrl + "/" + metadata.fileName == self.serverUrl { + reloadDataSource() + } else if withPush, metadata.serverUrl == self.serverUrl { + reloadDataSource() + if let sceneIdentifier = userInfo["sceneIdentifier"] as? String { + if sceneIdentifier == controller?.sceneIdentifier { + pushMetadata(metadata) + } + } else { + pushMetadata(metadata) } } + } + + @objc func favoriteFile(_ notification: NSNotification) { + if isSearchingMode { + return networkSearch() + } + + if self is NCFavorite { + return reloadDataSource() + } + + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + serverUrl == self.serverUrl + else { return } + + reloadDataSource() + } + + @objc func downloadStartFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func downloadedFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func downloadCancelFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func uploadStartFile(_ notification: NSNotification) { + collectionView?.reloadData() + } + + @objc func uploadedFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func uploadedLivePhoto(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func uploadCancelFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } - if serverUrl == homeServer || numFoldersLayoutsForView == 1 { - changeLayout(withSubFolders: false) + @objc func updateShare(_ notification: NSNotification) { + if isSearchingMode { + networkSearch() } else { - let alertController = UIAlertController(title: NSLocalizedString("_propagate_layout_", comment: ""), message: nil, preferredStyle: .alert) + self.dataSource.removeAll() + getServerData() + } + } + + // MARK: - Layout + + func setNavigationLeftItems() { + guard layoutKey == global.layoutViewFiles, + let tableAccount = database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) else { + return } + let image = utility.loadUserImage(for: tableAccount.user, displayName: tableAccount.displayName, urlBase: tableAccount.urlBase) + let accountButton = AccountSwitcherButton(type: .custom) + let accounts = database.getAllAccountOrderAlias() + var childrenAccountSubmenu: [UIMenuElement] = [] + + accountButton.setImage(image, for: .normal) + accountButton.setImage(image, for: .highlighted) + accountButton.semanticContentAttribute = .forceLeftToRight + accountButton.sizeToFit() + + if !accounts.isEmpty { + let accountActions: [UIAction] = accounts.map { account in + let image = utility.loadUserImage(for: account.user, displayName: account.displayName, urlBase: account.urlBase) + var name: String = "" + var url: String = "" + + if account.alias.isEmpty { + name = account.displayName + url = (URL(string: account.urlBase)?.host ?? "") + } else { + name = account.alias + } + + let action = UIAction(title: name, image: image, state: account.active ? .on : .off) { _ in + if !account.active { + NCAccount().changeAccount(account.account, userProfile: nil, controller: self.controller) { } + self.setEditMode(false) + } + } + + action.subtitle = url + return action + } + + let addAccountAction = UIAction(title: NSLocalizedString("_add_account_", comment: ""), image: utility.loadImage(named: "person.crop.circle.badge.plus", colors: NCBrandColor.shared.iconImageMultiColors)) { _ in + self.appDelegate.openLogin(selector: self.global.introLogin) + } + + let settingsAccountAction = UIAction(title: NSLocalizedString("_account_settings_", comment: ""), image: utility.loadImage(named: "gear", colors: [NCBrandColor.shared.iconImageColor])) { _ in + let accountSettingsModel = NCAccountSettingsModel(controller: self.controller, delegate: self) + let accountSettingsView = NCAccountSettingsView(model: accountSettingsModel) + let accountSettingsController = UIHostingController(rootView: accountSettingsView) + self.present(accountSettingsController, animated: true, completion: nil) + } + + if !NCBrandOptions.shared.disable_multiaccount { + childrenAccountSubmenu.append(addAccountAction) + } + childrenAccountSubmenu.append(settingsAccountAction) + + let addAccountSubmenu = UIMenu(title: "", options: .displayInline, children: childrenAccountSubmenu) + let menu = UIMenu(children: accountActions + [addAccountSubmenu]) + + accountButton.menu = menu + accountButton.showsMenuAsPrimaryAction = true + + accountButton.onMenuOpened = { + self.dismissTip() + } + } + + navigationItem.leftItemsSupplementBackButton = true + navigationItem.setLeftBarButtonItems([UIBarButtonItem(customView: accountButton)], animated: true) + + if titlePreviusFolder != nil { + navigationController?.navigationBar.topItem?.title = titlePreviusFolder + } + + navigationItem.title = titleCurrentFolder + } + + func setNavigationRightItems() { + guard layoutKey != global.layoutViewTransfers else { return } + let isTabBarHidden = self.tabBarController?.tabBar.isHidden ?? true + let isTabBarSelectHidden = tabBarSelect.isHidden() + + func createMenuActions() -> [UIMenuElement] { + guard let layoutForView = database.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) else { return [] } + + let select = UIAction(title: NSLocalizedString("_select_", comment: ""), + image: utility.loadImage(named: "checkmark.circle"), + attributes: (self.dataSource.isEmpty() || NCNetworking.shared.isOffline) ? .disabled : []) { _ in + self.setEditMode(true) + self.collectionView.reloadData() + } + + let list = UIAction(title: NSLocalizedString("_list_", comment: ""), image: utility.loadImage(named: "list.bullet"), state: layoutForView.layout == global.layoutList ? .on : .off) { _ in + + layoutForView.layout = self.global.layoutList + + NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, + object: nil, + userInfo: ["account": self.session.account, + "serverUrl": self.serverUrl, + "layoutForView": layoutForView]) + } + + let grid = UIAction(title: NSLocalizedString("_icons_", comment: ""), image: utility.loadImage(named: "square.grid.2x2"), state: layoutForView.layout == global.layoutGrid ? .on : .off) { _ in + + layoutForView.layout = self.global.layoutGrid + + NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, + object: nil, + userInfo: ["account": self.session.account, + "serverUrl": self.serverUrl, + "layoutForView": layoutForView]) + } + + let mediaSquare = UIAction(title: NSLocalizedString("_media_square_", comment: ""), image: utility.loadImage(named: "square.grid.3x3"), state: layoutForView.layout == global.layoutPhotoSquare ? .on : .off) { _ in + + layoutForView.layout = self.global.layoutPhotoSquare + + NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, + object: nil, + userInfo: ["account": self.session.account, + "serverUrl": self.serverUrl, + "layoutForView": layoutForView]) + } + + let mediaRatio = UIAction(title: NSLocalizedString("_media_ratio_", comment: ""), image: utility.loadImage(named: "rectangle.grid.3x2"), state: layoutForView.layout == self.global.layoutPhotoRatio ? .on : .off) { _ in + + layoutForView.layout = self.global.layoutPhotoRatio + + NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, + object: nil, + userInfo: ["account": self.session.account, + "serverUrl": self.serverUrl, + "layoutForView": layoutForView]) + } + + let viewStyleSubmenu = UIMenu(title: "", options: .displayInline, children: [list, grid, mediaSquare, mediaRatio]) + + let ascending = layoutForView.ascending + let ascendingChevronImage = utility.loadImage(named: ascending ? "chevron.up" : "chevron.down") + let isName = layoutForView.sort == "fileName" + let isDate = layoutForView.sort == "date" + let isSize = layoutForView.sort == "size" + + let byName = UIAction(title: NSLocalizedString("_name_", comment: ""), image: isName ? ascendingChevronImage : nil, state: isName ? .on : .off) { _ in + + if isName { // repeated press + layoutForView.ascending = !layoutForView.ascending + } + layoutForView.sort = "fileName" + + NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, + object: nil, + userInfo: ["account": self.session.account, + "serverUrl": self.serverUrl, + "layoutForView": layoutForView]) + } + + let byNewest = UIAction(title: NSLocalizedString("_date_", comment: ""), image: isDate ? ascendingChevronImage : nil, state: isDate ? .on : .off) { _ in + + if isDate { // repeated press + layoutForView.ascending = !layoutForView.ascending + } + layoutForView.sort = "date" + + NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, + object: nil, + userInfo: ["account": self.session.account, + "serverUrl": self.serverUrl, + "layoutForView": layoutForView]) + } + + let byLargest = UIAction(title: NSLocalizedString("_size_", comment: ""), image: isSize ? ascendingChevronImage : nil, state: isSize ? .on : .off) { _ in + + if isSize { // repeated press + layoutForView.ascending = !layoutForView.ascending + } + layoutForView.sort = "size" + + NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, + object: nil, + userInfo: ["account": self.session.account, + "serverUrl": self.serverUrl, + "layoutForView": layoutForView]) + } + + let sortSubmenu = UIMenu(title: NSLocalizedString("_order_by_", comment: ""), options: .displayInline, children: [byName, byNewest, byLargest]) - alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default, handler: { _ in - changeLayout(withSubFolders: true) - })) - alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", comment: ""), style: .default, handler: { _ in - changeLayout(withSubFolders: false) - })) + let foldersOnTop = UIAction(title: NSLocalizedString("_directory_on_top_no_", comment: ""), image: utility.loadImage(named: "folder"), state: layoutForView.directoryOnTop ? .on : .off) { _ in - self.present(alertController, animated: true) + layoutForView.directoryOnTop = !layoutForView.directoryOnTop + + NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, + object: nil, + userInfo: ["account": self.session.account, + "serverUrl": self.serverUrl, + "layoutForView": layoutForView]) + } + + let personalFilesOnly = NCKeychain().getPersonalFilesOnly(account: session.account) + let personalFilesOnlyAction = UIAction(title: NSLocalizedString("_personal_files_only_", comment: ""), image: utility.loadImage(named: "folder.badge.person.crop", colors: NCBrandColor.shared.iconImageMultiColors), state: personalFilesOnly ? .on : .off) { _ in + + NCKeychain().setPersonalFilesOnly(account: self.session.account, value: !personalFilesOnly) + + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource, userInfo: ["serverUrl": self.serverUrl, "clearDataSource": true]) + self.setNavigationRightItems() + } + + let showDescriptionKeychain = NCKeychain().showDescription + let showDescription = UIAction(title: NSLocalizedString("_show_description_", comment: ""), image: utility.loadImage(named: "list.dash.header.rectangle"), attributes: richWorkspaceText == nil ? .disabled : [], state: showDescriptionKeychain && richWorkspaceText != nil ? .on : .off) { _ in + + NCKeychain().showDescription = !showDescriptionKeychain + + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource, userInfo: ["serverUrl": self.serverUrl, "clearDataSource": true]) + self.setNavigationRightItems() + } + showDescription.subtitle = richWorkspaceText == nil ? NSLocalizedString("_no_description_available_", comment: "") : "" + + if layoutKey == global.layoutViewRecent { + return [select] + } else { + var additionalSubmenu = UIMenu() + if layoutKey == global.layoutViewFiles { + additionalSubmenu = UIMenu(title: "", options: .displayInline, children: [foldersOnTop, personalFilesOnlyAction, showDescription]) + } else { + additionalSubmenu = UIMenu(title: "", options: .displayInline, children: [foldersOnTop, showDescription]) + } + return [select, viewStyleSubmenu, sortSubmenu, additionalSubmenu] + } + } + + if isEditMode { + /// Magentacloud branding changes hide options on bottom tab bar +// tabBarSelect.update(selectOcId: selectOcId, metadatas: getSelectedMetadatas(), userId: appDelegate.userId) +// tabBarSelect.show() + let select = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: .done) { + self.setEditMode(false) + self.collectionView.reloadData() + } + navigationItem.rightBarButtonItems = [select] + } else if navigationItem.rightBarButtonItems == nil || (!isEditMode && !tabBarSelect.isHidden()) { + /// Magentacloud branding changes hide options on bottom tab bar +// tabBarSelect.hide() + let menuButton = UIBarButtonItem(image: utility.loadImage(named: "ellipsis.circle"), menu: UIMenu(children: createMenuActions())) + menuButton.tintColor = NCBrandColor.shared.iconImageColor + if layoutKey == global.layoutViewFiles { + let notification = UIBarButtonItem(image: utility.loadImage(named: "bell"), style: .plain) { + if let viewController = UIStoryboard(name: "NCNotification", bundle: nil).instantiateInitialViewController() as? NCNotification { + viewController.session = self.session + self.navigationController?.pushViewController(viewController, animated: true) + } + } + notification.tintColor = NCBrandColor.shared.iconImageColor + navigationItem.rightBarButtonItems = [menuButton, notification] + } else { + navigationItem.rightBarButtonItems = [menuButton] + } + } else { + navigationItem.rightBarButtonItems?.first?.menu = navigationItem.rightBarButtonItems?.first?.menu?.replacingChildren(createMenuActions()) + } + + if isEditMode { + let more = UIBarButtonItem(image: UIImage(systemName: "ellipsis"), style: .plain) { self.presentMenu(with: self.createMenuActions())} + navigationItem.rightBarButtonItems = [more] + } else { + let select = UIBarButtonItem(title: NSLocalizedString("_select_", comment: ""), style: UIBarButtonItem.Style.plain) { self.toggleSelect() } + if layoutKey == NCGlobal.shared.layoutViewFiles { + let notification = UIBarButtonItem(image: utility.loadImage(named: "bell"), style: .plain) { + if let viewController = UIStoryboard(name: "NCNotification", bundle: nil).instantiateInitialViewController() as? NCNotification { + self.navigationController?.pushViewController(viewController, animated: true) + } + } + notification.tintColor = NCBrandColor.shared.iconImageColor + navigationItem.rightBarButtonItems = [select, notification] + } else { + navigationItem.rightBarButtonItems = [select] + } } + guard layoutKey == NCGlobal.shared.layoutViewFiles else { return } + navigationItem.title = titleCurrentFolder } func getNavigationTitle() -> String { - let tblAccount = self.database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) - if let tblAccount, - !tblAccount.alias.isEmpty { - return tblAccount.alias + let tableAccount = self.database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) + if let tableAccount, + !tableAccount.alias.isEmpty { + return tableAccount.alias } return NCBrandOptions.shared.brand } - func accountSettingsDidDismiss(tblAccount: tableAccount?, controller: NCMainTabBarController?) { } + func accountSettingsDidDismiss(tableAccount: tableAccount?, controller: NCMainTabBarController?) { } + + func resetPlusButtonAlpha(animated: Bool = true) { } + + func isHiddenPlusButton(_ isHidden: Bool) { } @MainActor func startGUIGetServerData() { @@ -584,7 +1128,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // TIP dismissTip() // - mainNavigationController?.hiddenPlusButton(true) + isHiddenPlusButton(true) } func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { @@ -604,7 +1148,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS await self.reloadDataSource() } // - mainNavigationController?.hiddenPlusButton(false) + isHiddenPlusButton(false) } // MARK: - TAP EVENT @@ -655,7 +1199,30 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func tapButtonSwitch(_ sender: Any) { + guard !isTransitioning else { return } + isTransitioning = true + + guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: appDelegate.account, key: layoutKey, serverUrl: serverUrl) else { return } + + if layoutForView.layout == NCGlobal.shared.layoutGrid { + layoutForView.layout = NCGlobal.shared.layoutList + } else { + layoutForView.layout = NCGlobal.shared.layoutGrid + } + self.layoutForView = NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView) + self.collectionView.reloadData() + self.collectionView.collectionViewLayout.invalidateLayout() + self.collectionView.setCollectionViewLayout(layoutForView.layout == NCGlobal.shared.layoutList ? self.listLayout : self.gridLayout, animated: true) {_ in self.isTransitioning = false } + } + + func tapButtonOrder(_ sender: Any) { + let sortMenu = NCSortMenu() + sortMenu.toggleMenu(viewController: self, account: appDelegate.account, key: layoutKey, sortButton: sender as? UIButton, serverUrl: serverUrl) + } + + func longPressListItem(with objectId: String, indexPath: IndexPath, gestureRecognizer: UILongPressGestureRecognizer) { + } func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } @@ -786,26 +1353,32 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - DataSource - @MainActor func reloadDataSource() async { - if !isSearchingMode { - Task.detached { - if await self.isRecommendationActived() { + if isSearchingMode { + isDirectoryEncrypted = false + } else { + isDirectoryEncrypted = NCUtilityFileSystem().isDirectoryE2EE(session: session, serverUrl: serverUrl) + if isRecommendationActived { + Task.detached { await self.networking.createRecommendations(session: self.session, serverUrl: self.serverUrl, collectionView: self.collectionView) } } } - UIView.transition(with: self.collectionView, - duration: 0.20, - options: .transitionCrossDissolve, - animations: { self.collectionView.reloadData() }, - completion: nil) + DispatchQueue.main.async { + UIView.transition(with: self.collectionView, + duration: 0.20, + options: .transitionCrossDissolve, + animations: { self.collectionView.reloadData() }, + completion: nil) - await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + } } - func getServerData(forced: Bool = false) async { } + func getServerData(refresh: Bool = false) async { + dataSourceTask?.cancel() + } @objc func networkSearch() { guard !networkSearchInProgress else { @@ -816,7 +1389,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS !literalSearch.isEmpty else { return } - let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() self.networkSearchInProgress = true self.dataSource.removeAll() @@ -826,18 +1398,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS if capabilities.serverVersionMajor >= global.nextcloudVersion20 { self.networking.unifiedSearchFiles(literal: literalSearch, account: session.account) { task in - self.searchDataSourceTask = task + self.dataSourceTask = task Task { await self.reloadDataSource() } } providers: { account, searchProviders in self.providers = searchProviders self.searchResults = [] - self.dataSource = NCCollectionViewDataSource(metadatas: [], - layoutForView: self.layoutForView, - providers: self.providers, - searchResults: self.searchResults, - account: account) + self.dataSource = NCCollectionViewDataSource(metadatas: [], layoutForView: self.layoutForView, providers: self.providers, searchResults: self.searchResults, account: account) } update: { _, _, searchResult, metadatas in guard let metadatas, !metadatas.isEmpty, self.isSearchingMode, let searchResult else { return } self.networking.unifiedSearchQueue.addOperation(NCCollectionViewUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult)) @@ -849,7 +1417,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } else { self.networking.searchFiles(literal: literalSearch, account: session.account) { task in - self.searchDataSourceTask = task + self.dataSourceTask = task Task { await self.reloadDataSource() } @@ -868,11 +1436,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS withLayout: self.layoutForView, withAccount: self.session.account) - self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, - layoutForView: self.layoutForView, - providers: self.providers, - searchResults: self.searchResults, - account: self.session.account) + self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, layoutForView: self.layoutForView, providers: self.providers, searchResults: self.searchResults, account: self.session.account) self.networkSearchInProgress = false await self.reloadDataSource() } @@ -887,7 +1451,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.collectionView?.reloadData() self.networking.unifiedSearchFilesProvider(id: lastSearchResult.id, term: term, limit: 5, cursor: cursor, account: session.account) { task in - self.searchDataSourceTask = task + self.dataSourceTask = task Task { await self.reloadDataSource() } @@ -915,15 +1479,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - Push metadata func pushMetadata(_ metadata: tableMetadata) { - guard let navigationCollectionViewCommon = self.controller?.navigationCollectionViewCommon else { - return - } - let serverUrlPush = utilityFileSystem.createServerUrl(serverUrl: metadata.serverUrl, fileName: metadata.fileName) - - // Set Last Opening Date - Task { - await database.setDirectoryLastOpeningDateAsync(ocId: metadata.ocId) - } + guard let navigationCollectionViewCommon = self.controller?.navigationCollectionViewCommon else { return } + let serverUrlPush = utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName) if let viewController = navigationCollectionViewCommon.first(where: { $0.navigationController == self.navigationController && $0.serverUrl == serverUrlPush})?.viewController, viewController.isViewLoaded { navigationController?.pushViewController(viewController, animated: true) @@ -942,28 +1499,31 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - Header size - func getHeaderHeight(section: Int) -> (heightHeaderRichWorkspace: CGFloat, - heightHeaderRecommendations: CGFloat, - heightHeaderSection: CGFloat) { - var heightHeaderRichWorkspace: CGFloat = 0 - var heightHeaderRecommendations: CGFloat = 0 - var heightHeaderSection: CGFloat = 0 + func getHeaderHeight(section: Int) -> (heightHeaderCommands: CGFloat, heightHeaderRichWorkspace: CGFloat, heightHeaderSection: CGFloat) { + var headerRichWorkspace: CGFloat = 0 - if showDescription, - !isSearchingMode, - let richWorkspaceText = self.richWorkspaceText, - !richWorkspaceText.trimmingCharacters(in: .whitespaces).isEmpty { - heightHeaderRichWorkspace = UIScreen.main.bounds.size.height / 6 - } + func getHeaderHeight() -> CGFloat { + var size: CGFloat = 0 - if isRecommendationActived, - !isSearchingMode, - NCPreferences().showRecommendedFiles, - !self.database.getRecommendedFiles(account: self.session.account).isEmpty { - heightHeaderRecommendations = self.heightHeaderRecommendations - heightHeaderSection = self.heightHeaderSection + if isHeaderMenuTransferViewEnabled() != nil { + if !isSearchingMode { + size += global.heightHeaderTransfer + } + } + if headerMenuButtonsView { + size += NCGlobal.shared.heightButtonsView + } + return size } +// if isRecommendationActived, +// !isSearchingMode, +// NCKeychain().showRecommendedFiles, +// !self.database.getRecommendedFiles(account: self.session.account).isEmpty { +// heightHeaderRecommendations = self.heightHeaderRecommendations +// heightHeaderSection = self.heightHeaderSection +// } + if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { if section == 0 { return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) @@ -995,25 +1555,21 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - Footer size func sizeForFooterInSection(section: Int) -> CGSize { - guard let controller else { - return CGSize.zero - } let sections = dataSource.numberOfSections() - let bottomAreaInsets: CGFloat = controller.tabBar.safeAreaInsets.bottom == 0 ? 34 : 0 - let height = controller.tabBar.frame.height + bottomAreaInsets - - if isEditMode { - return CGSize(width: collectionView.frame.width, height: 90 + height) - } - - if isSearchingMode { - return CGSize(width: collectionView.frame.width, height: 50) - } + let metadataForSection = self.dataSource.getMetadataForSection(section) + let isPaginated = metadataForSection?.lastSearchResult?.isPaginated ?? false + let metadatasCount: Int = metadataForSection?.lastSearchResult?.entries.count ?? 0 + var size = CGSize(width: collectionView.frame.width, height: 0) if section == sections - 1 { - return CGSize(width: collectionView.frame.width, height: height) + size.height += 85 } else { - return CGSize(width: collectionView.frame.width, height: 0) + size.height += 1 + } + + if isSearchingMode && isPaginated && metadatasCount > 0 { + size.height += 30 } + return size } } diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index 3a3f982288..b0f882de81 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -1,22 +1,65 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2018 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCSectionFirstHeader.swift +// Nextcloud +// +// Created by Marino Faggiana on 09/10/2018. +// Copyright © 2018 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// import UIKit import MarkdownKit import NextcloudKit protocol NCSectionFirstHeaderDelegate: AnyObject { + func tapButtonSwitch(_ sender: Any) + func tapButtonOrder(_ sender: Any) + func tapButtonTransfer(_ sender: Any) func tapRichWorkspace(_ sender: Any) func tapRecommendations(with metadata: tableMetadata) func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) } +extension NCSectionFirstHeaderDelegate { + func tapButtonSwitch(_ sender: Any) {} + func tapButtonOrder(_ sender: Any) {} +} + class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegate { + + @IBOutlet weak var buttonSwitch: UIButton! + @IBOutlet weak var buttonOrder: UIButton! + @IBOutlet weak var buttonTransfer: UIButton! + @IBOutlet weak var imageButtonTransfer: UIImageView! + @IBOutlet weak var labelTransfer: UILabel! + @IBOutlet weak var progressTransfer: UIProgressView! + @IBOutlet weak var transferSeparatorBottom: UIView! + @IBOutlet weak var textViewRichWorkspace: UITextView! + @IBOutlet weak var labelSection: UILabel! + @IBOutlet weak var viewTransfer: UIView! @IBOutlet weak var viewRichWorkspace: UIView! @IBOutlet weak var viewRecommendations: UIView! @IBOutlet weak var viewSection: UIView! + @IBOutlet weak var viewButtonsView: UIView! + @IBOutlet weak var viewSeparator: UIView! + @IBOutlet weak var viewTransferHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var viewButtonsViewHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var viewSeparatorHeightConstraint: NSLayoutConstraint! @IBOutlet weak var viewRichWorkspaceHeightConstraint: NSLayoutConstraint! @IBOutlet weak var viewRecommendationsHeightConstraint: NSLayoutConstraint! @IBOutlet weak var viewSectionHeightConstraint: NSLayoutConstraint! @@ -45,6 +88,18 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat richWorkspaceGradient.startPoint = CGPoint(x: 0, y: 0.8) richWorkspaceGradient.endPoint = CGPoint(x: 0, y: 0.9) viewRichWorkspace.layer.addSublayer(richWorkspaceGradient) + backgroundColor = .clear + + //Button + buttonSwitch.setImage(UIImage(systemName: "list.bullet"), for: .normal)//!.image(color: NCBrandColor.shared.iconColor, size: 25), for: .normal) + + buttonOrder.setTitle("", for: .normal) + buttonOrder.setTitleColor(NCBrandColor.shared.brand, for: .normal) + + // Gradient +// gradient.startPoint = CGPoint(x: 0, y: 0.8) +// gradient.endPoint = CGPoint(x: 0, y: 0.9) +// viewRichWorkspace.layer.addSublayer(gradient) let tap = UITapGestureRecognizer(target: self, action: #selector(touchUpInsideViewRichWorkspace(_:))) tap.delegate = self @@ -73,6 +128,19 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat // labelSection.text = "" viewSectionHeightConstraint.constant = 0 + + buttonTransfer.backgroundColor = .clear + buttonTransfer.setImage(nil, for: .normal) + buttonTransfer.layer.cornerRadius = 6 + buttonTransfer.layer.masksToBounds = true + imageButtonTransfer.image = NCUtility().loadImage(named: "stop.circle") + imageButtonTransfer.tintColor = .white + labelTransfer.text = "" + progressTransfer.progress = 0 + progressTransfer.tintColor = NCBrandColor.shared.brandElement + progressTransfer.trackTintColor = NCBrandColor.shared.brandElement.withAlphaComponent(0.2) + transferSeparatorBottom.backgroundColor = .separator + transferSeparatorBottomHeightConstraint.constant = 0.5 } override func layoutSublayers(of layer: CALayer) { @@ -82,6 +150,47 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat setRichWorkspaceColor() } + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + setRichWorkspaceColor() + } + + // MARK: - View + + func setStatusButtonsView(enable: Bool) { + + buttonSwitch.isEnabled = enable + buttonOrder.isEnabled = enable + } + + func setImageSwitchList() { + + buttonSwitch.setImage(UIImage(systemName: "list.bullet"), for: .normal)//!.image(color: NCBrandColor.shared.iconColor, width: 20, height: 15), for: .normal) + } + + func setImageSwitchGrid() { + + buttonSwitch.setImage(UIImage(systemName: "square.grid.2x2")!.image(color: NCBrandColor.shared.iconImageColor, size: 20), for: .normal) + } + + func setButtonsView(height: CGFloat) { + + viewButtonsViewHeightConstraint.constant = height + if height == 0 { + viewButtonsView.isHidden = true + } else { + viewButtonsView.isHidden = false + } + } + + func setSortedTitle(_ title: String) { + + let title = NSLocalizedString(title, comment: "") + buttonOrder.setTitle(title, for: .normal) + } + + // MARK: - RichWorkspace func setContent(heightHeaderRichWorkspace: CGFloat, richWorkspaceText: String?, heightHeaderRecommendations: CGFloat, @@ -94,7 +203,7 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat viewRichWorkspaceHeightConstraint.constant = heightHeaderRichWorkspace viewRecommendationsHeightConstraint.constant = heightHeaderRecommendations viewSectionHeightConstraint.constant = heightHeaderSection - + if let richWorkspaceText, richWorkspaceText != self.richWorkspaceText { textViewRichWorkspace.attributedText = markdownParser.parse(richWorkspaceText) self.richWorkspaceText = richWorkspaceText @@ -105,17 +214,48 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat self.viewController = viewController self.sceneIdentifier = sceneItentifier self.delegate = delegate - + if heightHeaderRichWorkspace != 0, let richWorkspaceText, !richWorkspaceText.isEmpty { viewRichWorkspace.isHidden = false } else { viewRichWorkspace.isHidden = true } + } + + func setRichWorkspaceText(_ text: String?) { + guard let text = text else { return } + + if text != self.richWorkspaceText { + textViewRichWorkspace.attributedText = markdownParser.parse(text) + self.richWorkspaceText = text + } + } - if heightHeaderRecommendations != 0 && !recommendations.isEmpty { - viewRecommendations.isHidden = false + // MARK: - Transfer + + func setViewTransfer(isHidden: Bool, ocId: String? = nil, text: String? = nil, progress: Float? = nil) { + labelTransfer.text = text + viewTransfer.isHidden = isHidden + progressTransfer.progress = 0 + + if isHidden { + viewTransferHeightConstraint.constant = 0 } else { - viewRecommendations.isHidden = true + var image: UIImage? + if let ocId, + let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) { + image = utility.getIcon(metadata: metadata)?.darken() + if image == nil { + image = utility.loadImage(named: metadata.iconName, useTypeIconFile: true) + buttonTransfer.backgroundColor = .lightGray + } else { + buttonTransfer.backgroundColor = .clear + } + } + viewTransferHeightConstraint.constant = NCGlobal.shared.heightHeaderTransfer + if let progress { + progressTransfer.progress = progress + } } if heightHeaderSection == 0 { @@ -129,13 +269,27 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat // MARK: - RichWorkspace - func setRichWorkspaceColor(style: UIUserInterfaceStyle? = nil) { - if let style { - richWorkspaceGradient.colors = style == .light ? [UIColor(white: 1, alpha: 0).cgColor, UIColor.white.cgColor] : [UIColor(white: 0, alpha: 0).cgColor, UIColor.black.cgColor] + private func setRichWorkspaceColor() { + if traitCollection.userInterfaceStyle == .dark { + richWorkspaceGradient.colors = [UIColor(white: 0, alpha: 0).cgColor, UIColor.black.cgColor] } else { - richWorkspaceGradient.colors = traitCollection.userInterfaceStyle == .light ? [UIColor(white: 1, alpha: 0).cgColor, UIColor.white.cgColor] : [UIColor(white: 0, alpha: 0).cgColor, UIColor.black.cgColor] + richWorkspaceGradient.colors = [UIColor(white: 1, alpha: 0).cgColor, UIColor.white.cgColor] } } + + // MARK: - Action + + @IBAction func touchUpInsideSwitch(_ sender: Any) { + delegate?.tapButtonSwitch(sender) + } + + @IBAction func touchUpInsideOrder(_ sender: Any) { + delegate?.tapButtonOrder(sender) + } + + @IBAction func touchUpTransfer(_ sender: Any) { + delegate?.tapButtonTransfer(sender) + } @objc func touchUpInsideViewRichWorkspace(_ sender: Any) { delegate?.tapRichWorkspace(sender) @@ -152,7 +306,7 @@ extension NCSectionFirstHeader: UICollectionViewDataSource { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? NCRecommendationsCell else { fatalError() } if let metadata = NCManageDatabase.shared.getMetadataFromFileId(recommendedFiles.id) { - let imagePreview = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: global.previewExt512, userId: metadata.userId, urlBase: metadata.urlBase) + let imagePreview = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: global.previewExt512) if metadata.directory { cell.image.image = self.utility.loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) @@ -165,17 +319,10 @@ extension NCSectionFirstHeader: UICollectionViewDataSource { cell.image.contentMode = .scaleAspectFit if recommendedFiles.hasPreview { Task { - let resultsPreview = await NextcloudKit.shared.downloadPreviewAsync(fileId: metadata.fileId, etag: metadata.etag, account: metadata.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: metadata.account, - path: metadata.fileId, - name: "DownloadPreview") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } + let resultsPreview = await NextcloudKit.shared.downloadPreviewAsync(fileId: metadata.fileId, etag: metadata.etag, account: metadata.account) if resultsPreview.error == .success, let data = resultsPreview.responseData?.data { - self.utility.createImageFileFrom(data: data, ocId: metadata.ocId, etag: metadata.etag, userId: metadata.userId, urlBase: metadata.urlBase) - if let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: self.global.previewExt512, userId: metadata.userId, urlBase: metadata.urlBase) { + self.utility.createImageFileFrom(data: data, ocId: metadata.ocId, etag: metadata.etag) + if let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: self.global.previewExt512) { Task { @MainActor in for case let cell as NCRecommendationsCell in self.collectionViewRecommendations.visibleCells { if cell.id == recommendedFiles.id { @@ -233,7 +380,7 @@ extension NCSectionFirstHeader: UICollectionViewDelegate { return nil } let identifier = indexPath as NSCopying - let image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal().previewExt1024, userId: metadata.userId, urlBase: metadata.urlBase) + let image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal().previewExt1024) #if EXTENSION return nil diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.xib b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.xib index 1206a44a6a..528b2f0098 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.xib +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.xib @@ -1,24 +1,76 @@ - + - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -26,98 +78,138 @@ - - - - - + + + + + - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + - - + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + - + + + + - - + + diff --git a/iOSClient/Menu/NCMenuAction.swift b/iOSClient/Menu/NCMenuAction.swift index ddef43ec19..76ad642905 100644 --- a/iOSClient/Menu/NCMenuAction.swift +++ b/iOSClient/Menu/NCMenuAction.swift @@ -215,6 +215,21 @@ extension NCMenuAction { } ) } + + /// Copy files to pasteboard + static func copyAction(selectOcId: [String], viewController: UIViewController, order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction { + NCMenuAction( + title: NSLocalizedString("_copy_file_", comment: ""), + icon: NCUtility().loadImage(named: "copy", colors: [NCBrandColor.shared.iconImageColor]), + order: order, + action: { _ in + NCActionCenter.shared.copyPasteboard(pasteboardOcIds: selectOcId, viewController: viewController) + completion?() + } + ) + } + + /// Open view that lets the user move or copy the files within Nextcloud static func moveOrCopyAction(selectedMetadatas: [tableMetadata], account: String, viewController: UIViewController, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { NCMenuAction( diff --git a/iOSClient/Menu/NCSortMenu.swift b/iOSClient/Menu/NCSortMenu.swift new file mode 100644 index 0000000000..3aaadb4dcd --- /dev/null +++ b/iOSClient/Menu/NCSortMenu.swift @@ -0,0 +1,149 @@ +// +// NCSortMenu.swift +// Nextcloud +// +// Created by Marino Faggiana on 27/08/2020. +// Copyright © 2020 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +import UIKit +import FloatingPanel +import NextcloudKit + +class NCSortMenu: NSObject { + + private var sortButton: UIButton? + private var serverUrl: String = "" + private var hideDirectoryOnTop: Bool? + + private var key = "" + + func toggleMenu(viewController: UIViewController, account: String, key: String, sortButton: UIButton?, serverUrl: String, hideDirectoryOnTop: Bool = false) { + + self.key = key + self.sortButton = sortButton + self.serverUrl = serverUrl + self.hideDirectoryOnTop = hideDirectoryOnTop + + guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: account, key: key, serverUrl: serverUrl) else { return } + var actions = [NCMenuAction]() + var title = "" + var icon = UIImage() + + if layoutForView.ascending { + title = NSLocalizedString("_order_by_name_z_a_", comment: "") + icon = UIImage(named: "sortFileNameZA")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) + } else { + title = NSLocalizedString("_order_by_name_a_z_", comment: "") + icon = UIImage(named: "sortFileNameAZ")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) + } + + actions.append( + NCMenuAction( + title: title, + icon: icon, + selected: layoutForView.sort == "fileName", + on: layoutForView.sort == "fileName", + action: { _ in + layoutForView.sort = "fileName" + layoutForView.ascending = !layoutForView.ascending + self.actionMenu(layoutForView: layoutForView) + } + ) + ) + + if layoutForView.ascending { + title = NSLocalizedString("_order_by_date_more_recent_", comment: "") + icon = UIImage(named: "sortDateMoreRecent")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) + } else { + title = NSLocalizedString("_order_by_date_less_recent_", comment: "") + icon = UIImage(named: "sortDateLessRecent")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) + } + + actions.append( + NCMenuAction( + title: title, + icon: icon, + selected: layoutForView.sort == "date", + on: layoutForView.sort == "date", + action: { _ in + layoutForView.sort = "date" + layoutForView.ascending = !layoutForView.ascending + self.actionMenu(layoutForView: layoutForView) + } + ) + ) + + if layoutForView.ascending { + title = NSLocalizedString("_order_by_size_largest_", comment: "") + icon = UIImage(named: "sortLargest")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) + } else { + title = NSLocalizedString("_order_by_size_smallest_", comment: "") + icon = UIImage(named: "sortSmallest")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) + } + + actions.append( + NCMenuAction( + title: title, + icon: icon, + selected: layoutForView.sort == "size", + on: layoutForView.sort == "size", + action: { _ in + layoutForView.sort = "size" + layoutForView.ascending = !layoutForView.ascending + self.actionMenu(layoutForView: layoutForView) + } + ) + ) + + if !hideDirectoryOnTop { + actions.append( + NCMenuAction( + title: NSLocalizedString("_directory_on_top_no_", comment: ""), + icon: UIImage(named: "foldersOnTop")!.image(color: NCBrandColor.shared.iconImageColor, size: 50), + selected: layoutForView.directoryOnTop, + on: layoutForView.directoryOnTop, + action: { _ in + layoutForView.directoryOnTop = !layoutForView.directoryOnTop + self.actionMenu(layoutForView: layoutForView) + } + ) + ) + } + + viewController.presentMenu(with: actions) + } + + func actionMenu(layoutForView: NCDBLayoutForView) { + + switch layoutForView.sort { + case "fileName": + layoutForView.titleButtonHeader = layoutForView.ascending ? "_sorted_by_name_a_z_" : "_sorted_by_name_z_a_" + case "date": + layoutForView.titleButtonHeader = layoutForView.ascending ? "_sorted_by_date_less_recent_" : "_sorted_by_date_more_recent_" + case "size": + layoutForView.titleButtonHeader = layoutForView.ascending ? "_sorted_by_size_smallest_" : "_sorted_by_size_largest_" + default: + break + } + + self.sortButton?.setTitle(NSLocalizedString(layoutForView.titleButtonHeader, comment: ""), for: .normal) + NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView) + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) + } +} diff --git a/iOSClient/Networking/NCDownloadAction.swift b/iOSClient/Networking/NCDownloadAction.swift new file mode 100644 index 0000000000..082ceb63b9 --- /dev/null +++ b/iOSClient/Networking/NCDownloadAction.swift @@ -0,0 +1,735 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2020 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import UIKit +import NextcloudKit +import Queuer +import SVGKit +import Photos +import Alamofire + +class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSelectDelegate, NCTransferDelegate { + static let shared = NCDownloadAction() + + var viewerQuickLook: NCViewerQuickLook? + var documentController: UIDocumentInteractionController? + let utilityFileSystem = NCUtilityFileSystem() + let utility = NCUtility() + let global = NCGlobal.shared + var sceneIdentifier: String = "" + + override private init() { } + + func setup(sceneIdentifier: String) { + self.sceneIdentifier = sceneIdentifier + + Task { + await NCNetworking.shared.transferDispatcher.addDelegate(self) + } + } + + // MARK: - Download + + func transferChange(status: String, metadata: tableMetadata, error: NKError) { + DispatchQueue.main.async { + switch status { + /// DOWNLOADED + case self.global.networkingStatusDownloaded: + self.downloadedFile(metadata: metadata, error: error) + default: + break + } + } + } + + func downloadedFile(metadata: tableMetadata, error: NKError) { + guard error == .success else { + return + } + /// Select UIWindowScene active in serverUrl + var controller: NCMainTabBarController? + let windowScenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene } + if windowScenes.count == 1 { + controller = UIApplication.shared.firstWindow?.rootViewController as? NCMainTabBarController + } else if let sceneIdentifier = metadata.sceneIdentifier, + let tabBarController = SceneManager.shared.getController(sceneIdentifier: sceneIdentifier) { + controller = tabBarController + } else { + for windowScene in windowScenes { + if let rootViewController = windowScene.keyWindow?.rootViewController as? NCMainTabBarController, + rootViewController.currentServerUrl() == metadata.serverUrl { + controller = rootViewController + break + } + } + } + guard let controller else { return } + + switch metadata.sessionSelector { + case NCGlobal.shared.selectorLoadFileQuickLook: + + let fileNamePath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase) + let fileNameTemp = NSTemporaryDirectory() + metadata.fileNameView + let viewerQuickLook = NCViewerQuickLook(with: URL(fileURLWithPath: fileNameTemp), isEditingEnabled: true, metadata: metadata) + if let image = UIImage(contentsOfFile: fileNamePath) { + if let data = image.jpegData(compressionQuality: 1) { + do { + try data.write(to: URL(fileURLWithPath: fileNameTemp)) + } catch { + return + } + } + let navigationController = UINavigationController(rootViewController: viewerQuickLook) + navigationController.modalPresentationStyle = .fullScreen + controller.present(navigationController, animated: true) + } else { + self.utilityFileSystem.copyFile(atPath: fileNamePath, toPath: fileNameTemp) + controller.present(viewerQuickLook, animated: true) + } + + case NCGlobal.shared.selectorLoadFileView: + guard !isAppInBackground + else { + return + } + + if metadata.contentType.contains("opendocument") && !self.utility.isTypeFileRichDocument(metadata) { + self.openActivityViewController(selectedMetadata: [metadata], controller: controller, sender: nil) + } else if metadata.classFile == NKTypeClassFile.compress.rawValue || metadata.classFile == NKTypeClassFile.unknow.rawValue { + self.openActivityViewController(selectedMetadata: [metadata], controller: controller, sender: nil) + } else { + if let viewController = controller.currentViewController() { + let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt1024, userId: metadata.userId, urlBase: metadata.urlBase) + Task { + if let vc = await NCViewer().getViewerController(metadata: metadata, image: image, delegate: viewController) { + await viewController.navigationController?.pushViewController(vc, animated: true) + } + } + } + } + + case NCGlobal.shared.selectorOpenIn: + guard !isAppInBackground + else { + return + } + + self.openActivityViewController(selectedMetadata: [metadata], controller: controller, sender: nil) + + case NCGlobal.shared.selectorSaveAlbum: + + self.saveAlbum(metadata: metadata, controller: controller) + + case NCGlobal.shared.selectorSaveAsScan: + + self.saveAsScan(metadata: metadata, controller: controller) + + case NCGlobal.shared.selectorOpenDetail: + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterOpenMediaDetail, userInfo: ["ocId": metadata.ocId]) + + default: + let applicationHandle = NCApplicationHandle() + applicationHandle.downloadedFile(selector: metadata.sessionSelector, metadata: metadata) + } + } + + // MARK: - + + func setMetadataAvalableOffline(_ metadata: tableMetadata, isOffline: Bool) async { + if isOffline { + if metadata.directory { + await NCManageDatabase.shared.setDirectoryAsync(serverUrl: metadata.serverUrlFileName, offline: false, metadata: metadata) + let predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND sessionSelector == %@ AND status == %d", metadata.account, metadata.serverUrlFileName, NCGlobal.shared.selectorSynchronizationOffline, NCGlobal.shared.metadataStatusWaitDownload) + if let metadatas = await NCManageDatabase.shared.getMetadatasAsync(predicate: predicate) { + await NCManageDatabase.shared.clearMetadatasSessionAsync(metadatas: metadatas) + } + } else { + await NCManageDatabase.shared.setOffLocalFileAsync(ocId: metadata.ocId) + } + } else if metadata.directory { + await NCManageDatabase.shared.cleanTablesOcIds(account: metadata.account, userId: metadata.userId, urlBase: metadata.urlBase) + await NCManageDatabase.shared.setDirectoryAsync(serverUrl: metadata.serverUrlFileName, offline: true, metadata: metadata) + await NCNetworking.shared.synchronization(account: metadata.account, serverUrl: metadata.serverUrlFileName, userId: metadata.userId, urlBase: metadata.urlBase, metadatasInDownload: nil) + } else { + var metadatasSynchronizationOffline: [tableMetadata] = [] + metadatasSynchronizationOffline.append(metadata) + if let metadata = await NCManageDatabase.shared.getMetadataLivePhotoAsync(metadata: metadata) { + metadatasSynchronizationOffline.append(metadata) + } + await NCManageDatabase.shared.addLocalFilesAsync(metadatas: [metadata], offline: true) + for metadata in metadatasSynchronizationOffline { + await NCManageDatabase.shared.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, + session: NCNetworking.shared.sessionDownloadBackground, + selector: NCGlobal.shared.selectorSynchronizationOffline) + } + } + } + + // MARK: - + + @MainActor + func viewerFile(account: String, fileId: String, viewController: UIViewController) async { + var downloadRequest: DownloadRequest? + let hud = NCHud(viewController.tabBarController?.view) + + if let metadata = await NCManageDatabase.shared.getMetadataFromFileIdAsync(fileId) { + do { + let attr = try FileManager.default.attributesOfItem(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase)) + let fileSize = attr[FileAttributeKey.size] as? UInt64 ?? 0 + if fileSize > 0 { + if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: viewController) { + viewController.navigationController?.pushViewController(vc, animated: true) + } + return + } + } catch { + print("Error: \(error)") + } + } + + hud.ringProgress(tapToCancelDetailText: true) { + if let request = downloadRequest { + request.cancel() + } + } + + let resultsFile = await NextcloudKit.shared.getFileFromFileIdAsync(fileId: fileId, account: account) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, + path: fileId, + name: "getFileFromFileId") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + hud.dismiss() + guard resultsFile.error == .success, let file = resultsFile.file else { + NCContentPresenter().showError(error: resultsFile.error) + return + } + + let metadata = await NCManageDatabase.shared.convertFileToMetadataAsync(file) + await NCManageDatabase.shared.addMetadataAsync(metadata) + + let fileNameLocalPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase) + + if metadata.isAudioOrVideo { + if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: viewController) { + viewController.navigationController?.pushViewController(vc, animated: true) + } + return + } + + hud.show() + let download = await NextcloudKit.shared.downloadAsync(serverUrlFileName: metadata.serverUrlFileName, fileNameLocalPath: fileNameLocalPath, account: account) { request in + downloadRequest = request + } taskHandler: { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: metadata.account, + path: metadata.serverUrlFileName, + name: "download") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + + let ocId = metadata.ocId + await NCManageDatabase.shared.setMetadataSessionAsync(ocId: ocId, + sessionTaskIdentifier: task.taskIdentifier, + status: self.global.metadataStatusDownloading) + } + } progressHandler: { progress in + hud.progress(progress.fractionCompleted) + } + + hud.dismiss() + await NCManageDatabase.shared.setMetadataSessionAsync(ocId: metadata.ocId, + session: "", + sessionTaskIdentifier: 0, + sessionError: "", + status: self.global.metadataStatusNormal, + etag: download.etag) + + if download.nkError == .success { + await NCManageDatabase.shared.addLocalFilesAsync(metadatas: [metadata]) + if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: viewController) { + viewController.navigationController?.pushViewController(vc, animated: true) + } + } + } + + // MARK: - + + func openShare(viewController: UIViewController, metadata: tableMetadata, page: NCBrandOptions.NCInfoPagingTab) { + var page = page + let capabilities = NCNetworking.shared.capabilities[metadata.account] ?? NKCapabilities.Capabilities() + + NCActivityIndicator.shared.start(backgroundView: viewController.view) + NCNetworking.shared.readFile(serverUrlFileName: metadata.serverUrlFileName, account: metadata.account) { _, metadata, file, error in + Task { @MainActor in + NCActivityIndicator.shared.stop() + + if let metadata = metadata, let file = file, error == .success { + // Remove all known download limits from shares related to the given file. + // This avoids obsolete download limit objects to stay around. + // Afterwards create new download limits, should any such be returned for the known shares. + let shares = await NCManageDatabase.shared.getTableSharesAsync(account: metadata.account, + serverUrl: metadata.serverUrl, + fileName: metadata.fileName) + for share in shares { + await NCManageDatabase.shared.deleteDownloadLimitAsync(byAccount: metadata.account, shareToken: share.token) + + if let receivedDownloadLimit = file.downloadLimits.first(where: { $0.token == share.token }) { + await NCManageDatabase.shared.createDownloadLimitAsync(account: metadata.account, + count: receivedDownloadLimit.count, + limit: receivedDownloadLimit.limit, + token: receivedDownloadLimit.token) + } + } + + var pages: [NCBrandOptions.NCInfoPagingTab] = [] + let shareNavigationController = UIStoryboard(name: "NCShare", bundle: nil).instantiateInitialViewController() as? UINavigationController + let shareViewController = shareNavigationController?.topViewController as? NCSharePaging + + for value in NCBrandOptions.NCInfoPagingTab.allCases { + pages.append(value) + } + if capabilities.activity.isEmpty, let idx = pages.firstIndex(of: .activity) { + pages.remove(at: idx) + } + if !metadata.isSharable(), let idx = pages.firstIndex(of: .sharing) { + pages.remove(at: idx) + } + + (pages, page) = NCApplicationHandle().filterPages(pages: pages, page: page, metadata: metadata) + + shareViewController?.pages = pages + shareViewController?.metadata = metadata + + if pages.contains(page) { + shareViewController?.page = page + } else if let page = pages.first { + shareViewController?.page = page + } else { + return + } + + shareNavigationController?.modalPresentationStyle = .formSheet + if let shareNavigationController = shareNavigationController { + viewController.present(shareNavigationController, animated: true, completion: nil) + } + } + } + } + } + + // MARK: - Open Activity [Share] ... + + func openActivityViewController(selectedMetadata: [tableMetadata], controller: NCMainTabBarController?, sender: Any?) { + guard let controller else { return } + let metadatas = selectedMetadata.filter({ !$0.directory }) + var urls: [URL] = [] + var downloadMetadata: [(tableMetadata, URL)] = [] + + for metadata in metadatas { + let fileURL = URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase)) + if utilityFileSystem.fileProviderStorageExists(metadata) { + urls.append(fileURL) + } else { + downloadMetadata.append((metadata, fileURL)) + } + } + + let processor = ParallelWorker(n: 5, titleKey: "_downloading_", totalTasks: downloadMetadata.count, controller: controller) + for (metadata, url) in downloadMetadata { + processor.execute { completion in + Task { + guard let metadata = await NCManageDatabase.shared.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, + session: NCNetworking.shared.sessionDownload, + selector: "", + sceneIdentifier: controller.sceneIdentifier) else { + return completion() + } + + await NCNetworking.shared.downloadFile(metadata: metadata) { _ in + } progressHandler: { progress in + processor.hud.progress(progress.fractionCompleted) + } + + if self.utilityFileSystem.fileProviderStorageExists(metadata) { + urls.append(url) + } + completion() + } + } + } + + processor.completeWork { + guard !urls.isEmpty else { return } + let activityViewController = UIActivityViewController(activityItems: urls, applicationActivities: nil) + + // iPad + if let popover = activityViewController.popoverPresentationController { + if let view = sender as? UIView { + popover.sourceView = view + popover.sourceRect = view.bounds + } else { + popover.sourceView = controller.view + popover.sourceRect = CGRect(x: controller.view.bounds.midX, + y: controller.view.bounds.midY, + width: 0, + height: 0) + popover.permittedArrowDirections = [] + } + } + + controller.present(activityViewController, animated: true) + } + } + + // MARK: - Save as scan + + func saveAsScan(metadata: tableMetadata, controller: NCMainTabBarController?) { + let fileNamePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase) + let fileNameDestination = utilityFileSystem.createFileName("scan.png", fileDate: Date(), fileType: PHAssetMediaType.image, notUseMask: true) + let fileNamePathDestination = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryScan, fileName: fileNameDestination) + + utilityFileSystem.copyFile(atPath: fileNamePath, toPath: fileNamePathDestination) + + if let navigationController = UIStoryboard(name: "NCScan", bundle: nil).instantiateInitialViewController() { + navigationController.modalPresentationStyle = UIModalPresentationStyle.pageSheet + let viewController = navigationController.presentedViewController as? NCScan + viewController?.serverUrl = controller?.currentServerUrl() + viewController?.controller = controller + controller?.present(navigationController, animated: true, completion: nil) + } + } + + // MARK: - Save photo + + func saveAlbum(metadata: tableMetadata, controller: NCMainTabBarController?) { + let fileNamePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase) + + NCAskAuthorization().askAuthorizationPhotoLibrary(controller: controller) { hasPermission in + guard hasPermission else { + let error = NKError(errorCode: NCGlobal.shared.errorFileNotSaved, errorDescription: "_access_photo_not_enabled_msg_") + return NCContentPresenter().messageNotification("_access_photo_not_enabled_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error) + } + + let errorSave = NKError(errorCode: NCGlobal.shared.errorFileNotSaved, errorDescription: "_file_not_saved_cameraroll_") + + do { + if metadata.isImage { + let data = try Data(contentsOf: URL(fileURLWithPath: fileNamePath)) + PHPhotoLibrary.shared().performChanges({ + let assetRequest = PHAssetCreationRequest.forAsset() + assetRequest.addResource(with: .photo, data: data, options: nil) + }) { success, _ in + if !success { + NCContentPresenter().messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error) + } + } + } else if metadata.isVideo { + PHPhotoLibrary.shared().performChanges({ + PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: fileNamePath)) + }) { success, _ in + if !success { + NCContentPresenter().messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error) + } + } + } else { + NCContentPresenter().messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error) + return + } + } catch { + NCContentPresenter().messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error) + } + } + } + + // MARK: - Copy & Paste + + func copyPasteboard(pasteboardOcIds: [String], viewController: UIViewController) { + var items = [[String: Any]]() + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } + let hudView = viewController.view + var fractionCompleted: Float = 0 + + // getting file data can take some time and block the main queue + DispatchQueue.global(qos: .userInitiated).async { + var downloadMetadatas: [tableMetadata] = [] + for ocid in pasteboardOcIds { + guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocid) else { continue } + if let pasteboardItem = metadata.toPasteBoardItem() { + items.append(pasteboardItem) + } else { + downloadMetadatas.append(metadata) + } + } + + // do 5 downloads in parallel to optimize efficiency + let processor = ParallelWorker(n: 5, titleKey: "_downloading_", totalTasks: downloadMetadatas.count, hudView: hudView) + + for metadata in downloadMetadatas { + processor.execute { completion in + guard let metadata = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata], + session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload, + selector: "") else { return completion() } + NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: false) { + } requestHandler: { _ in + } progressHandler: { progress in + if Float(progress.fractionCompleted) > fractionCompleted || fractionCompleted == 0 { + processor.hud?.progress = Float(progress.fractionCompleted) + fractionCompleted = Float(progress.fractionCompleted) + } + } completion: { _, _ in + fractionCompleted = 0 + completion() + } + } + } + processor.completeWork { + items.append(contentsOf: downloadMetadatas.compactMap({ $0.toPasteBoardItem() })) + UIPasteboard.general.setItems(items, options: [:]) + } + } + } + + + // MARK: - Copy & Paste + + func pastePasteboard(serverUrl: String, account: String, controller: NCMainTabBarController?) async { + var fractionCompleted: Float = 0 + let processor = ParallelWorker(n: 5, titleKey: "_status_uploading_", totalTasks: nil, controller: controller) + guard let tblAccount = await NCManageDatabase.shared.getTableAccountAsync(account: account) else { + return + } + + func uploadPastePasteboard(fileName: String, serverUrlFileName: String, fileNameLocalPath: String, serverUrl: String, completion: @escaping () -> Void) { + NextcloudKit.shared.upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, account: account) { _ in + } taskHandler: { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, + path: serverUrlFileName, + name: "upload") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } progressHandler: { progress in + if Float(progress.fractionCompleted) > fractionCompleted || fractionCompleted == 0 { + processor.hud.progress(progress.fractionCompleted) + fractionCompleted = Float(progress.fractionCompleted) + } + } completionHandler: { account, ocId, etag, _, _, _, error in + if error == .success && etag != nil && ocId != nil { + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(ocId!, + fileName: fileName, + userId: tblAccount.userId, + urlBase: tblAccount.urlBase) + self.utilityFileSystem.moveFile(atPath: fileNameLocalPath, toPath: toPath) + NCManageDatabase.shared.addLocalFile(account: account, etag: etag!, ocId: ocId!, fileName: fileName) + Task { + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferRequestData(serverUrl: serverUrl) + } + } + } else { + NCContentPresenter().showError(error: error) + } + fractionCompleted = 0 + completion() + } + } + + for (index, items) in UIPasteboard.general.items.enumerated() { + for item in items { + let capabilities = await NKCapabilities.shared.getCapabilities(for: account) + let results = NKFilePropertyResolver().resolve(inUTI: item.key, capabilities: capabilities) + guard let data = UIPasteboard.general.data(forPasteboardType: item.key, inItemSet: IndexSet([index]))?.first else { + continue + } + let fileName = results.name + "_" + NCPreferences().incrementalNumber + "." + results.ext + let serverUrlFileName = utilityFileSystem.createServerUrl(serverUrl: serverUrl, fileName: fileName) + let ocIdUpload = UUID().uuidString + let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(ocIdUpload, + fileName: fileName, + userId: tblAccount.userId, + urlBase: tblAccount.urlBase) + do { try data.write(to: URL(fileURLWithPath: fileNameLocalPath)) } catch { continue } + processor.execute { completion in + uploadPastePasteboard(fileName: fileName, serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, serverUrl: serverUrl, completion: completion) + } + } + } + processor.completeWork() + } + + // MARK: - + + func openFileViewInFolder(serverUrl: String, fileNameBlink: String?, fileNameOpen: String?, sceneIdentifier: String) { + guard let controller = SceneManager.shared.getController(sceneIdentifier: sceneIdentifier), + let navigationController = controller.viewControllers?.first as? UINavigationController + else { return } + let session = NCSession.shared.getSession(controller: controller) + var serverUrlPush = self.utilityFileSystem.getHomeServer(session: session) + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + navigationController.popToRootViewController(animated: false) + controller.selectedIndex = 0 + if serverUrlPush == serverUrl, + let viewController = navigationController.topViewController as? NCFiles { + viewController.blinkCell(fileName: fileNameBlink) + viewController.openFile(fileName: fileNameOpen) + return + } + + let diffDirectory = serverUrl.replacingOccurrences(of: serverUrlPush, with: "") + var subDirs = diffDirectory.split(separator: "/") + + while serverUrlPush != serverUrl, !subDirs.isEmpty { + + guard let dir = subDirs.first else { + return + } + serverUrlPush = self.utilityFileSystem.createServerUrl(serverUrl: serverUrlPush, fileName: String(dir)) + + if let viewController = controller.navigationCollectionViewCommon.first(where: { $0.navigationController == navigationController && $0.serverUrl == serverUrlPush})?.viewController as? NCFiles, viewController.isViewLoaded { + viewController.fileNameBlink = fileNameBlink + viewController.fileNameOpen = fileNameOpen + navigationController.pushViewController(viewController, animated: false) + } else { + if let viewController: NCFiles = UIStoryboard(name: "NCFiles", bundle: nil).instantiateInitialViewController() as? NCFiles { + viewController.serverUrl = serverUrlPush + viewController.titleCurrentFolder = String(dir) + viewController.navigationItem.backButtonTitle = viewController.titleCurrentFolder + + controller.navigationCollectionViewCommon.append(NavigationCollectionViewCommon(serverUrl: serverUrlPush, navigationController: navigationController, viewController: viewController)) + + if serverUrlPush == serverUrl { + viewController.fileNameBlink = fileNameBlink + viewController.fileNameOpen = fileNameOpen + } + navigationController.pushViewController(viewController, animated: false) + } + } + subDirs.remove(at: 0) + } + } + } + + // MARK: - NCSelect + Delegate + + func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], overwrite: Bool, copy: Bool, move: Bool, session: NCSession.Session) { + if let destination = serverUrl, !items.isEmpty { + if copy { + for case let metadata as tableMetadata in items { + if metadata.status != NCGlobal.shared.metadataStatusNormal, metadata.status != NCGlobal.shared.metadataStatusWaitCopy { + continue + } + + NCNetworking.shared.copyMetadata(metadata, destination: destination, overwrite: overwrite) + } + + } else if move { + for case let metadata as tableMetadata in items { + if metadata.status != NCGlobal.shared.metadataStatusNormal, metadata.status != NCGlobal.shared.metadataStatusWaitMove { + continue + } + + NCNetworking.shared.moveMetadata(metadata, destination: destination, overwrite: overwrite) + } + } + } + } + + func openSelectView(items: [tableMetadata], controller: NCMainTabBarController?) { + let session = NCSession.shared.getSession(controller: controller) + let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController + let topViewController = navigationController?.topViewController as? NCSelect + var listViewController = [NCSelect]() + var copyItems: [tableMetadata] = [] + let capabilities = NCNetworking.shared.capabilities[controller?.account ?? ""] ?? NKCapabilities.Capabilities() + + for item in items { + if let fileNameError = FileNameValidator.checkFileName(item.fileNameView, account: controller?.account, capabilities: capabilities) { + controller?.present(UIAlertController.warning(message: "\(fileNameError.errorDescription) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) + return + } + copyItems.append(item) + } + + let home = utilityFileSystem.getHomeServer(session: session) + var serverUrl = copyItems[0].serverUrl + + // Setup view controllers such that the current view is of the same directory the items to be copied are in + while true { + // If not in the topmost directory, create a new view controller and set correct title. + // If in the topmost directory, use the default view controller as the base. + var viewController: NCSelect? + if serverUrl != home { + viewController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateViewController(withIdentifier: "NCSelect.storyboard") as? NCSelect + if viewController == nil { + return + } + viewController!.titleCurrentFolder = (serverUrl as NSString).lastPathComponent + } else { + viewController = topViewController + } + guard let vc = viewController else { return } + + vc.delegate = self + vc.typeOfCommandView = .copyMove + vc.items = copyItems + vc.serverUrl = serverUrl + vc.session = session + + vc.navigationItem.backButtonTitle = vc.titleCurrentFolder + listViewController.insert(vc, at: 0) + + if serverUrl != home { + if let serverDirectoryUp = utilityFileSystem.serverDirectoryUp(serverUrl: serverUrl, home: home) { + serverUrl = serverDirectoryUp + } + } else { + break + } + } + + navigationController?.setViewControllers(listViewController, animated: false) + navigationController?.modalPresentationStyle = .formSheet + + if let navigationController = navigationController { + controller?.present(navigationController, animated: true, completion: nil) + } + } +} + + +fileprivate extension tableMetadata { + func toPasteBoardItem() -> [String: Any]? { + // Get Data + let fileUrl = URL(fileURLWithPath: NCUtilityFileSystem().getDirectoryProviderStorageOcId(ocId, fileNameView: fileNameView)) + guard NCUtilityFileSystem().fileProviderStorageExists(self), + let data = try? Data(contentsOf: fileUrl) else { return nil } + + // Determine the UTI for the file + guard let fileUTI = UTType(filenameExtension: fileExtension)?.identifier else { return nil } + + // Pasteboard item + return [fileUTI: data] + } +} diff --git a/iOSClient/Offline/NCOffline.swift b/iOSClient/Offline/NCOffline.swift index c677762068..7154170163 100644 --- a/iOSClient/Offline/NCOffline.swift +++ b/iOSClient/Offline/NCOffline.swift @@ -35,6 +35,7 @@ class NCOffline: NCCollectionViewCommon { enableSearchBar = false headerRichWorkspaceDisable = true emptyImageName = "icloud.and.arrow.down" + emptyImage = UIImage(named: "folder") emptyTitle = "_files_no_files_" emptyDescription = "_tutorial_offline_view_" emptyDataPortaitOffset = 30 From 7922e289b80d1a40e54f5be302032d10d1b13a74 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Thu, 30 Nov 2023 15:09:33 +0530 Subject: [PATCH 2/5] NMC 2172 - dashboard theming customisation --- ...nViewCommon+CollectionViewDataSource.swift | 17 +++++++++++++ .../NCCollectionViewCommon.swift | 25 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift index 41c4f2385f..3f86b1299f 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift @@ -442,6 +442,23 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.fileSharedLabel?.textColor = NCBrandColor.shared.notificationAction } + // Button More + if metadata.isInTransfer || metadata.isWaitingTransfer { + cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCImageCache.images.buttonStop) + } else if metadata.lock == true { + cell.setButtonMore(named: NCGlobal.shared.buttonMoreLock, image: NCImageCache.images.buttonMoreLock) + a11yValues.append(String(format: NSLocalizedString("_locked_by_", comment: ""), metadata.lockOwnerDisplayName)) + } else { + cell.fileSharedImage?.image = NCImageCache.images.canShare.image(color: NCBrandColor.shared.gray60, size: 50) + cell.fileSharedLabel?.text = "" + } + + if metadata.permissions.contains("S"), (metadata.permissions.range(of: "S") != nil) { + cell.fileSharedImage?.image = NCImageCache.images.sharedWithMe + cell.fileSharedLabel?.text = NSLocalizedString("_recieved_", comment: "") + cell.fileSharedLabel?.textColor = NCBrandColor.shared.notificationAction + } + // Button More if metadata.lock == true { cell.setButtonMore(image: imageCache.getImageButtonMoreLock()) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 9bf8db55a3..fbc1330258 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -1221,6 +1221,28 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS sortMenu.toggleMenu(viewController: self, account: appDelegate.account, key: layoutKey, sortButton: sender as? UIButton, serverUrl: serverUrl) } + func tapButtonSwitch(_ sender: Any) { + guard !isTransitioning else { return } + isTransitioning = true + + guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: appDelegate.account, key: layoutKey, serverUrl: serverUrl) else { return } + + if layoutForView.layout == NCGlobal.shared.layoutGrid { + layoutForView.layout = NCGlobal.shared.layoutList + } else { + layoutForView.layout = NCGlobal.shared.layoutGrid + } + self.layoutForView = NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView) + self.collectionView.reloadData() + self.collectionView.collectionViewLayout.invalidateLayout() + self.collectionView.setCollectionViewLayout(layoutForView.layout == NCGlobal.shared.layoutList ? self.listLayout : self.gridLayout, animated: true) {_ in self.isTransitioning = false } + } + + func tapButtonOrder(_ sender: Any) { + let sortMenu = NCSortMenu() + sortMenu.toggleMenu(viewController: self, account: appDelegate.account, key: layoutKey, sortButton: sender as? UIButton, serverUrl: serverUrl) + } + func longPressListItem(with objectId: String, indexPath: IndexPath, gestureRecognizer: UILongPressGestureRecognizer) { } @@ -1513,6 +1535,9 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS if headerMenuButtonsView { size += NCGlobal.shared.heightButtonsView } + if headerMenuButtonsView { + size += NCGlobal.shared.heightButtonsView + } return size } From aaf25100b9a0f0c9cd1bf06cdf423b019866562b Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 14 Apr 2025 16:33:57 +0530 Subject: [PATCH 3/5] NMC 2172 - Dashboard theming customisation changes --- Nextcloud.xcodeproj/project.pbxproj | 12 + .../Cell/NCCellProtocol.swift | 20 +- .../Collection Common/Cell/NCListCell.swift | 77 ++- ...nViewCommon+CollectionViewDataSource.swift | 382 +++++------ ...ctionViewCommon+SelectTabBarDelegate.swift | 137 ++-- .../NCCollectionViewCommon.swift | 626 +++++++----------- .../NCSectionFirstHeader.swift | 48 +- .../NCSectionFirstHeader.xib | 4 + iOSClient/Menu/NCSortMenu.swift | 22 +- iOSClient/Offline/NCOffline.swift | 4 +- 10 files changed, 650 insertions(+), 682 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 8dd4c5545f..b5e443587c 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -85,6 +85,11 @@ AFCE353527E4ED5900FEA6C2 /* DateFormatter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */; }; AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; }; AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* Shareable.swift */; }; + AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */; }; + B5C9801E2DACEB5A0041B146 /* NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C9801D2DACEB5A0041B146 /* NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift */; }; + B5C980202DAD201A0041B146 /* NCSortMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C9801F2DAD201A0041B146 /* NCSortMenu.swift */; }; + C04E2F232A17BB4D001BAD85 /* FilesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */; }; + D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; F310B1EF2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F310B1EE2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift */; }; F321DA8A2B71205A00DDA0E6 /* NCTrashSelectTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F321DA892B71205A00DDA0E6 /* NCTrashSelectTabBar.swift */; }; @@ -1221,6 +1226,9 @@ AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extension.swift"; sourceTree = ""; }; AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareCells.swift; sourceTree = ""; }; AFCE353827E5DE0400FEA6C2 /* Shareable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shareable.swift; sourceTree = ""; }; + AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShare+Helper.swift"; sourceTree = ""; }; + B5C9801D2DACEB5A0041B146 /* NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift"; sourceTree = ""; }; + B5C9801F2DAD201A0041B146 /* NCSortMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCSortMenu.swift; sourceTree = ""; }; C0046CDA2A17B98400D87C9D /* NextcloudUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; @@ -1986,6 +1994,7 @@ F376A3732E5CC5FF0067EE25 /* ContextMenuActions.swift */, F77A697C250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift */, F78C6FDD296D677300C952C3 /* NCContextMenu.swift */, + B5C9801F2DAD201A0041B146 /* NCSortMenu.swift */, 3704EB2923D5A58400455C5B /* NCMenu.storyboard */, 371B5A2D23D0B04500FAFAE9 /* NCMenu.swift */, AF935066276B84E700BD078F /* NCMenu+FloatingPanel.swift */, @@ -2464,6 +2473,7 @@ F7603298252F0E550015A421 /* Collection Common */ = { isa = PBXGroup; children = ( + B5C9801D2DACEB5A0041B146 /* NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift */, F75FE06B2BB01D0D00A0EFEF /* Cell */, F78ACD50219046AC0088454D /* Section Header Footer */, F70D7C3525FFBF81002B9E34 /* NCCollectionViewCommon.swift */, @@ -4470,6 +4480,7 @@ F72944F52A8424F800246839 /* NCEndToEndMetadataV1.swift in Sources */, F710D2022405826100A6033D /* NCViewerContextMenu.swift in Sources */, F765E9CD295C585800A09ED8 /* NCUploadScanDocument.swift in Sources */, + B5C980202DAD201A0041B146 /* NCSortMenu.swift in Sources */, F741C2242B6B9FD600E849BB /* NCMediaSelectTabBar.swift in Sources */, F77A697D250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift in Sources */, F7BF9D822934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */, @@ -4504,6 +4515,7 @@ F70898692EDDB51700EF85BD /* NCSelectOpen+SelectDelegate.swift in Sources */, F7D4BF412CA2E8D800A5E746 /* TOPasscodeViewControllerAnimatedTransitioning.m in Sources */, F7D4BF422CA2E8D800A5E746 /* TOPasscodeSettingsViewController.m in Sources */, + B5C9801E2DACEB5A0041B146 /* NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift in Sources */, F7D4BF432CA2E8D800A5E746 /* TOPasscodeCircleImage.m in Sources */, F78026102E9CFA3700B63436 /* NCTransfersView.swift in Sources */, F7D4BF442CA2E8D800A5E746 /* TOPasscodeView.m in Sources */, diff --git a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift index 9e969faed9..e1f97b5193 100644 --- a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift +++ b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift @@ -39,13 +39,13 @@ protocol NCCellProtocol { var fileSharedImage: UIImageView? { get set } var fileMoreImage: UIImageView? { get set } var cellSeparatorView: UIView? { get set } - var indexPath: IndexPath { get set } var fileSharedLabel: UILabel? { get set } + var fileProgressView: UIProgressView? { get set } func titleInfoTrailingDefault() func titleInfoTrailingFull() func writeInfoDateSize(date: NSDate, size: Int64) - func setButtonMore(image: UIImage) + func setButtonMore(named: String, image: UIImage) func hideImageItem(_ status: Bool) func hideImageFavorite(_ status: Bool) func hideImageStatus(_ status: Bool) @@ -55,6 +55,8 @@ protocol NCCellProtocol { func hideLabelSubinfo(_ status: Bool) func hideButtonShare(_ status: Bool) func hideButtonMore(_ status: Bool) + func selectMode(_ status: Bool) + func selected(_ status: Bool) func selected(_ status: Bool, isEditMode: Bool) func setAccessibility(label: String, value: String) func setTags(tags: [String]) @@ -121,11 +123,19 @@ extension NCCellProtocol { get { return nil } set { } } - + var fileProgressView: UIProgressView? { + get { return nil } + set {} + } + var fileSelectImage: UIImageView? { + get { return nil } + set {} + } + func titleInfoTrailingDefault() {} func titleInfoTrailingFull() {} func writeInfoDateSize(date: NSDate, size: Int64) {} - func setButtonMore(image: UIImage) {} + func setButtonMore(named: String, image: UIImage) {} func hideImageItem(_ status: Bool) {} func hideImageFavorite(_ status: Bool) {} func hideImageStatus(_ status: Bool) {} @@ -135,6 +145,8 @@ extension NCCellProtocol { func hideLabelSubinfo(_ status: Bool) {} func hideButtonShare(_ status: Bool) {} func hideButtonMore(_ status: Bool) {} + func selectMode(_ status: Bool) {} + func selected(_ status: Bool) {} func selected(_ status: Bool, isEditMode: Bool) {} func setAccessibility(label: String, value: String) {} func setTags(tags: [String]) {} diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index cbf7de1f82..563c220b3d 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -44,9 +44,9 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var separatorHeightConstraint: NSLayoutConstraint! @IBOutlet weak var subInfoTrailingConstraint: NSLayoutConstraint! - private var objectId = "" + private var ocId = "" + private var ocIdTransfer = "" private var user = "" - var indexPath = IndexPath() weak var listCellDelegate: NCListCellDelegate? var namedButtonMore = "" @@ -54,9 +54,9 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto var fileAvatarImageView: UIImageView? { return imageShared } - var fileObjectId: String? { - get { return objectId } - set { objectId = newValue ?? "" } + var fileOcId: String? { + get { return ocId } + set { ocId = newValue ?? "" } } var filePreviewImageView: UIImageView? { get { return imageItem } @@ -118,7 +118,11 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto override func awakeFromNib() { super.awakeFromNib() + initCell() + } + func initCell() { + imageItem.layer.cornerRadius = 6 imageItem.layer.masksToBounds = true @@ -143,6 +147,10 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto tag0.text = "" tag1.text = "" titleInfoTrailingDefault() + progressView.tintColor = NCBrandColor.shared.brand + progressView.transform = CGAffineTransform(scaleX: 1.0, y: 0.5) + progressView.trackTintColor = .clear + imageSelect.isHidden = true let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(gestureRecognizer:))) longPressedGesture.minimumPressDuration = 0.5 @@ -152,20 +160,21 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto separator.backgroundColor = .separator separatorHeightConstraint.constant = 0.5 + titleInfoTrailingDefault() labelTitle.text = "" labelInfo.text = "" labelTitle.textColor = .label labelInfo.textColor = .systemGray labelSubinfo.textColor = .systemGray + setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCImageCache.images.buttonMore) + imageMore.isHidden = false + buttonMore.isHidden = false } override func prepareForReuse() { super.prepareForReuse() - imageItem.backgroundColor = nil - accessibilityHint = nil - accessibilityLabel = nil - accessibilityValue = nil + initCell() } override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? { @@ -173,15 +182,19 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } @IBAction func touchUpInsideShare(_ sender: Any) { - listCellDelegate?.tapShareListItem(with: objectId, indexPath: indexPath, sender: sender) + listCellDelegate?.tapShareListItem(with: ocId, ocIdTransfer: ocIdTransfer, sender: sender) } @IBAction func touchUpInsideMore(_ sender: Any) { - listCellDelegate?.tapMoreListItem(with: objectId, namedButtonMore: namedButtonMore, image: imageItem.image, indexPath: indexPath, sender: sender) + listCellDelegate?.tapMoreListItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: imageItem.image, sender: sender) } @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { - listCellDelegate?.longPressListItem(with: objectId, indexPath: indexPath, gestureRecognizer: gestureRecognizer) + listCellDelegate?.longPressListItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, gestureRecognizer: gestureRecognizer) + } + + @objc func longPressInsideMore(gestureRecognizer: UILongPressGestureRecognizer) { + listCellDelegate?.longPressMoreListItem(with: ocId, namedButtonMore: namedButtonMore, gestureRecognizer: gestureRecognizer) } fileprivate func setA11yActions() { @@ -227,11 +240,10 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto separator.isHidden = status } - func selected(_ status: Bool, isEditMode: Bool) { - if isEditMode { + func selectMode(_ status: Bool) { + if status { imageItemLeftConstraint.constant = 45 imageSelect.isHidden = false - imageShared.isHidden = true imageMore.isHidden = true buttonShared.isHidden = true buttonMore.isHidden = true @@ -239,28 +251,46 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } else { imageItemLeftConstraint.constant = 10 imageSelect.isHidden = true - imageShared.isHidden = false imageMore.isHidden = false buttonShared.isHidden = false buttonMore.isHidden = false backgroundView = nil setA11yActions() } + } + + func selected(_ status: Bool, isEditMode: Bool) { + // NMC-1190 - iOS - Files - Deleting files while files are still uploading won't delete properly : to fix this issue remove check for !metadata.isInTransfer in below line + guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId), !metadata.isInTransfer, !metadata.e2eEncrypted else { + backgroundView = nil + separator.isHidden = false + imageSelect.isHidden = true + + return + } + if status { + var blurEffect: UIVisualEffect? var blurEffectView: UIView? - blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial)) - blurEffectView?.backgroundColor = .lightGray + if traitCollection.userInterfaceStyle == .dark { + blurEffect = UIBlurEffect(style: .dark) + blurEffectView = UIVisualEffectView(effect: blurEffect) + blurEffectView?.backgroundColor = .black + } else { + blurEffect = UIBlurEffect(style: .extraLight) + blurEffectView = UIVisualEffectView(effect: blurEffect) + blurEffectView?.backgroundColor = .lightGray + } blurEffectView?.frame = self.bounds blurEffectView?.autoresizingMask = [.flexibleWidth, .flexibleHeight] - imageSelect.image = NCImageCache.images.checkedYes backgroundView = blurEffectView + imageSelect.image = NCImageCache.images.checkedYes separator.isHidden = true } else { imageSelect.image = NCImageCache.images.checkedNo backgroundView = nil separator.isHidden = false } - } func writeInfoDateSize(date: NSDate, size: Int64) { @@ -338,9 +368,10 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } protocol NCListCellDelegate: AnyObject { - func tapShareListItem(with objectId: String, indexPath: IndexPath, sender: Any) - func tapMoreListItem(with objectId: String, namedButtonMore: String, image: UIImage?, indexPath: IndexPath, sender: Any) - func longPressListItem(with objectId: String, indexPath: IndexPath, gestureRecognizer: UILongPressGestureRecognizer) + func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) + func tapMoreListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) + func longPressMoreListItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) + func longPressListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) } // MARK: - List Layout diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift index 3f86b1299f..fe862508db 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift @@ -13,92 +13,15 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + let numberItems = dataSource.numberOfItemsInSection(section) + emptyDataSet?.numberOfItemsInSection(numberItems, section: section) // get auto upload folder self.autoUploadFileName = self.database.getAccountAutoUploadFileName(account: self.session.account) self.autoUploadDirectory = self.database.getAccountAutoUploadDirectory(session: self.session) // get layout for view self.layoutForView = self.database.getLayoutForView(account: self.session.account, key: self.layoutKey, serverUrl: self.serverUrl) - return self.dataSource.numberOfItemsInSection(section) - } - - func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - guard let metadata = dataSource.cellForItemAt(indexPath: indexPath), - let cell = (cell as? NCCellProtocol) else { return } - let existsIcon = utilityFileSystem.fileProviderStoragePreviewIconExists(metadata.ocId, etag: metadata.etag) - - func downloadAvatar(fileName: String, user: String, dispalyName: String?) { - if let image = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) { - cell.fileAvatarImageView?.contentMode = .scaleAspectFill - cell.fileAvatarImageView?.image = image - } else { - NCNetworking.shared.downloadAvatar(user: user, dispalyName: dispalyName, fileName: fileName, cell: cell, view: collectionView) - } - } - /// CONTENT MODE - cell.filePreviewImageView?.layer.borderWidth = 0 - if existsIcon { - cell.filePreviewImageView?.contentMode = .scaleAspectFill - } else { - cell.filePreviewImageView?.contentMode = .scaleAspectFit - } - cell.fileAvatarImageView?.contentMode = .center - /// THUMBNAIL - if !metadata.directory { - if metadata.hasPreviewBorder { - cell.filePreviewImageView?.layer.borderWidth = 0.2 - cell.filePreviewImageView?.layer.borderColor = UIColor.lightGray.cgColor - } - if metadata.name == NCGlobal.shared.appName { - if layoutForView?.layout == NCGlobal.shared.layoutPhotoRatio || layoutForView?.layout == NCGlobal.shared.layoutPhotoSquare { - if let image = NCImageCache.shared.getPreviewImageCache(ocId: metadata.ocId, etag: metadata.etag) { - cell.filePreviewImageView?.image = image - } else if let image = UIImage(contentsOfFile: self.utilityFileSystem.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)) { - cell.filePreviewImageView?.image = image - NCImageCache.shared.addPreviewImageCache(metadata: metadata, image: image) - } - } else { - if let image = NCImageCache.shared.getIconImageCache(ocId: metadata.ocId, etag: metadata.etag) { - cell.filePreviewImageView?.image = image - } else if metadata.hasPreview { - cell.filePreviewImageView?.image = utility.getIcon(metadata: metadata) - } - } - if cell.filePreviewImageView?.image == nil { - if metadata.iconName.isEmpty { - cell.filePreviewImageView?.image = NCImageCache.images.file - } else { - cell.filePreviewImageView?.image = utility.loadImage(named: metadata.iconName, useTypeIconFile: true) - } - if metadata.hasPreview && metadata.status == NCGlobal.shared.metadataStatusNormal && !existsIcon { - for case let operation as NCCollectionViewDownloadThumbnail in NCNetworking.shared.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId { return } - NCNetworking.shared.downloadThumbnailQueue.addOperation(NCCollectionViewDownloadThumbnail(metadata: metadata, cell: cell, collectionView: collectionView)) - } - } - } else { - /// APP NAME - UNIFIED SEARCH - switch metadata.iconName { - case let str where str.contains("contacts"): - cell.filePreviewImageView?.image = NCImageCache.images.iconContacts - case let str where str.contains("conversation"): - cell.filePreviewImageView?.image = NCImageCache.images.iconTalk - case let str where str.contains("calendar"): - cell.filePreviewImageView?.image = NCImageCache.images.iconCalendar - case let str where str.contains("deck"): - cell.filePreviewImageView?.image = NCImageCache.images.iconDeck - case let str where str.contains("mail"): - cell.filePreviewImageView?.image = NCImageCache.images.iconMail - case let str where str.contains("talk"): - cell.filePreviewImageView?.image = NCImageCache.images.iconTalk - case let str where str.contains("confirm"): - cell.filePreviewImageView?.image = NCImageCache.images.iconConfirm - case let str where str.contains("pages"): - cell.filePreviewImageView?.image = NCImageCache.images.iconPages - default: - cell.filePreviewImageView?.image = NCImageCache.images.iconFile - } - } - } + return numberItems } func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { @@ -180,8 +103,10 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { /// Edit mode if fileSelect.contains(metadata.ocId) { + cell.selectMode(true) cell.selected(true, isEditMode: isEditMode) } else { + cell.selectMode(false) cell.selected(false, isEditMode: isEditMode) } @@ -216,8 +141,10 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { listCell.listCellDelegate = self cell = listCell } - let metadata = self.dataSource.getMetadata(indexPath: indexPath) ?? tableMetadata() + guard let metadata = dataSource.cellForItemAt(indexPath: indexPath) else { return cell } let shares = NCManageDatabase.shared.getTableShares(metadata: metadata) + let existsImagePreview = utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag) + let ext = global.getSizeExtension(column: self.numberOfColumns) defer { if !metadata.isSharable() || (!capabilities.fileSharingApiEnabled && !capabilities.filesComments && capabilities.activity.isEmpty) { @@ -232,30 +159,6 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { utility.createImageFileFrom(metadata: metadata) } - // LAYOUT PHOTO - if isLayoutPhoto { - if metadata.isImageOrVideo { - let photoCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as? NCPhotoCell)! - photoCell.photoCellDelegate = self - cell = photoCell - return self.photoCell(cell: photoCell, indexPath: indexPath, metadata: metadata, ext: ext) - } else { - let gridCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridCell)! - gridCell.gridCellDelegate = self - cell = gridCell - } - } else if isLayoutGrid { - // LAYOUT GRID - let gridCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridCell)! - gridCell.gridCellDelegate = self - cell = gridCell - } else { - // LAYOUT LIST - let listCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as? NCListCell)! - listCell.listCellDelegate = self - cell = listCell - } - /// CONTENT MODE cell.fileAvatarImageView?.contentMode = .center cell.filePreviewImageView?.layer.borderWidth = 0 @@ -279,12 +182,24 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.fileOcId = metadata.ocId cell.fileOcIdTransfer = metadata.ocIdTransfer cell.fileUser = metadata.ownerId - + cell.fileSelectImage?.image = nil + cell.fileStatusImage?.image = nil + cell.fileLocalImage?.image = nil + cell.fileFavoriteImage?.image = nil + cell.fileMoreImage?.image = nil + cell.filePreviewImageView?.image = nil + cell.filePreviewImageView?.backgroundColor = nil + cell.fileProgressView?.isHidden = true + cell.fileProgressView?.progress = 0.0 + cell.hideButtonShare(false) + cell.hideButtonMore(false) + cell.titleInfoTrailingDefault() + if isSearchingMode { cell.fileTitleLabel?.text = metadata.fileName cell.fileTitleLabel?.lineBreakMode = .byTruncatingTail if metadata.name == global.appName { - cell.fileInfoLabel?.text = NSLocalizedString("_in_", comment: "") + " " + utilityFileSystem.getPath(path: metadata.path, user: metadata.user) + cell.fileInfoLabel?.text = utility.dateDiff(metadata.date as Date) + " · " + utilityFileSystem.transformedSize(metadata.size) } else { cell.fileInfoLabel?.text = metadata.subline } @@ -293,6 +208,14 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.fileTitleLabel?.text = metadata.fileNameView cell.fileSubinfoLabel?.isHidden = false cell.fileInfoLabel?.text = metadata.sessionError +// // Temporary issue fix for NMC-3771: iOS v9.1.6 > multiple uploads cause error messages +// if metadata.sessionError == "423: WebDAV Locked: Trying to access locked resource" || metadata.sessionError == "423: WebDAV gesperrt: Zugriffsversuch auf eine gesperrte Ressource" { +// cell.fileTitleLabel?.text = metadata.fileName +// cell.fileTitleLabel?.lineBreakMode = .byTruncatingMiddle +// } else { +// cell.fileSubinfoLabel?.isHidden = false +// cell.fileInfoLabel?.text = metadata.sessionError +// } } else { cell.fileSubinfoLabel?.isHidden = false cell.fileTitleLabel?.text = metadata.fileNameView @@ -300,6 +223,10 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.writeInfoDateSize(date: metadata.date, size: metadata.size) } + if metadata.status == NCGlobal.shared.metadataStatusDownloading || metadata.status == NCGlobal.shared.metadataStatusUploading { + cell.fileProgressView?.isHidden = false + } + // Accessibility [shared] if metadata.ownerId != appDelegate.userId, appDelegate.account == metadata.account { if metadata.ownerId != metadata.userId { a11yValues.append(NSLocalizedString("_shared_with_you_by_", comment: "") + " " + metadata.ownerDisplayName) @@ -309,9 +236,9 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { let tblDirectory = database.getTableDirectory(ocId: metadata.ocId) if metadata.e2eEncrypted { - cell.filePreviewImageView?.image = imageCache.getFolderEncrypted(account: metadata.account) + cell.filePreviewImageView?.image = imageCache.getFolderEncrypted() } else if isShare { - cell.filePreviewImageView?.image = imageCache.getFolderSharedWithMe(account: metadata.account) + cell.filePreviewImageView?.image = imageCache.getFolderSharedWithMe() } else if !metadata.shareType.isEmpty { metadata.shareType.contains(NKShare.ShareType.publicLink.rawValue) ? (cell.filePreviewImageView?.image = imageCache.getFolderPublic(account: metadata.account)) : @@ -319,13 +246,13 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } else if !metadata.shareType.isEmpty && metadata.shareType.contains(NKShare.ShareType.publicLink.rawValue) { cell.filePreviewImageView?.image = imageCache.getFolderPublic(account: metadata.account) } else if metadata.mountType == "group" { - cell.filePreviewImageView?.image = imageCache.getFolderGroup(account: metadata.account) + cell.filePreviewImageView?.image = imageCache.getFolderGroup() } else if isMounted { - cell.filePreviewImageView?.image = imageCache.getFolderExternal(account: metadata.account) + cell.filePreviewImageView?.image = imageCache.getFolderExternal() } else if metadata.fileName == autoUploadFileName && metadata.serverUrl == autoUploadDirectory { - cell.filePreviewImageView?.image = imageCache.getFolderAutomaticUpload(account: metadata.account) + cell.filePreviewImageView?.image = imageCache.getFolderAutomaticUpload() } else { - cell.filePreviewImageView?.image = imageCache.getFolder(account: metadata.account) + cell.filePreviewImageView?.image = imageCache.getFolder() } // Local image: offline @@ -347,6 +274,8 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if metadata.name == global.appName { if let image = NCImageCache.shared.getImageCache(ocId: metadata.ocId, etag: metadata.etag, ext: ext) { cell.filePreviewImageView?.image = image + } else if metadata.fileExtension == "odg" { + cell.filePreviewImageView?.image = UIImage(named: "diagram") } else if let image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext) { cell.filePreviewImageView?.image = image } @@ -362,23 +291,23 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { /// APP NAME - UNIFIED SEARCH switch metadata.iconName { case let str where str.contains("contacts"): - cell.filePreviewImageView?.image = utility.loadImage(named: "person.crop.rectangle.stack", colors: [NCBrandColor.shared.iconImageColor]) + cell.filePreviewImageView?.image = NCImageCache.images.iconContacts case let str where str.contains("conversation"): - cell.filePreviewImageView?.image = UIImage(named: "talk-template")!.image(color: NCBrandColor.shared.getElement(account: metadata.account)) + cell.filePreviewImageView?.image = NCImageCache.images.iconTalk case let str where str.contains("calendar"): - cell.filePreviewImageView?.image = utility.loadImage(named: "calendar", colors: [NCBrandColor.shared.iconImageColor]) + cell.filePreviewImageView?.image = NCImageCache.images.iconCalendar case let str where str.contains("deck"): - cell.filePreviewImageView?.image = utility.loadImage(named: "square.stack.fill", colors: [NCBrandColor.shared.iconImageColor]) + cell.filePreviewImageView?.image = NCImageCache.images.iconDeck case let str where str.contains("mail"): - cell.filePreviewImageView?.image = utility.loadImage(named: "mail", colors: [NCBrandColor.shared.iconImageColor]) + cell.filePreviewImageView?.image = NCImageCache.images.iconMail case let str where str.contains("talk"): - cell.filePreviewImageView?.image = UIImage(named: "talk-template")!.image(color: NCBrandColor.shared.getElement(account: metadata.account)) + cell.filePreviewImageView?.image = NCImageCache.images.iconTalk case let str where str.contains("confirm"): - cell.filePreviewImageView?.image = utility.loadImage(named: "arrow.right", colors: [NCBrandColor.shared.iconImageColor]) + cell.filePreviewImageView?.image = NCImageCache.images.iconConfirm case let str where str.contains("pages"): - cell.filePreviewImageView?.image = utility.loadImage(named: "doc.richtext", colors: [NCBrandColor.shared.iconImageColor]) + cell.filePreviewImageView?.image = NCImageCache.images.iconPages default: - cell.filePreviewImageView?.image = utility.loadImage(named: "doc", colors: [NCBrandColor.shared.iconImageColor]) + cell.filePreviewImageView?.image = NCImageCache.images.file } if !metadata.iconUrl.isEmpty { if let ownerId = getAvatarFromIconUrl(metadata: metadata) { @@ -421,7 +350,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { (cell.fileSharedImage?.image = imageCache.getImageShareByLink()) : (cell.fileSharedImage?.image = imageCache.getImageShared()) } else { - cell.fileSharedImage?.image = NCImageCache.images.canShare.image(color: NCBrandColor.shared.gray60, size: 50) + cell.fileSharedImage?.image = NCImageCache.images.canShare.image(color: NCBrandColor.shared.gray60) cell.fileSharedLabel?.text = "" } if appDelegate.account != metadata.account { @@ -432,7 +361,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if (!metadata.shareType.isEmpty || !(shares.share?.isEmpty ?? true) || (shares.firstShareLink != nil)){ cell.fileSharedImage?.image = cell.fileSharedImage?.image?.imageColor(NCBrandColor.shared.customer) } else { - cell.fileSharedImage?.image = NCImageCache.images.canShare.image(color: NCBrandColor.shared.gray60, size: 50) + cell.fileSharedImage?.image = NCImageCache.images.canShare.image(color: NCBrandColor.shared.gray60) cell.fileSharedLabel?.text = "" } @@ -449,22 +378,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.setButtonMore(named: NCGlobal.shared.buttonMoreLock, image: NCImageCache.images.buttonMoreLock) a11yValues.append(String(format: NSLocalizedString("_locked_by_", comment: ""), metadata.lockOwnerDisplayName)) } else { - cell.fileSharedImage?.image = NCImageCache.images.canShare.image(color: NCBrandColor.shared.gray60, size: 50) - cell.fileSharedLabel?.text = "" - } - - if metadata.permissions.contains("S"), (metadata.permissions.range(of: "S") != nil) { - cell.fileSharedImage?.image = NCImageCache.images.sharedWithMe - cell.fileSharedLabel?.text = NSLocalizedString("_recieved_", comment: "") - cell.fileSharedLabel?.textColor = NCBrandColor.shared.notificationAction - } - - // Button More - if metadata.lock == true { - cell.setButtonMore(image: imageCache.getImageButtonMoreLock()) - a11yValues.append(String(format: NSLocalizedString("_locked_by_", comment: ""), metadata.lockOwnerDisplayName)) - } else { - cell.setButtonMore(image: imageCache.getImageButtonMore()) + cell.setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCImageCache.images.buttonMore) } // Status @@ -503,25 +417,6 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { break } - // AVATAR - if !metadata.ownerId.isEmpty, metadata.ownerId != metadata.userId { - cell.fileAvatarImageView?.contentMode = .scaleAspectFill - - let fileName = NCSession.shared.getFileName(urlBase: metadata.urlBase, user: metadata.ownerId) - self.database.getImageAvatarLoaded(fileName: fileName) { image, tblAvatar in - if let image { - cell.fileAvatarImageView?.image = image - } else { - cell.fileAvatarImageView?.image = self.utility.loadUserImage(for: metadata.ownerId, displayName: metadata.ownerDisplayName, urlBase: metadata.urlBase) - } - - if !(tblAvatar?.loaded ?? false), - self.networking.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty { - self.networking.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: metadata.ownerId, fileName: fileName, account: metadata.account, view: collectionView)) - } - } - } - // URL if metadata.classFile == NKTypeClassFile.url.rawValue { cell.fileLocalImage?.image = nil @@ -540,11 +435,16 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } // Edit mode - if fileSelect.contains(metadata.ocId) { - cell.selected(true, isEditMode: isEditMode) - a11yValues.append(NSLocalizedString("_selected_", comment: "")) + if isEditMode { + cell.selectMode(true) + if fileSelect.contains(metadata.ocId) { + cell.selected(true, isEditMode: isEditMode) + a11yValues.append(NSLocalizedString("_selected_", comment: "")) + } else { + cell.selected(false, isEditMode: isEditMode) + } } else { - cell.selected(false, isEditMode: isEditMode) + cell.selectMode(false) } // Accessibility @@ -564,33 +464,6 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { // TAGS cell.setTags(tags: Array(metadata.tags)) - // Layout photo - if isLayoutPhoto { - let width = UIScreen.main.bounds.width / CGFloat(self.numberOfColumns) - - cell.hideImageFavorite(false) - cell.hideImageLocal(false) - cell.hideImageItem(false) - cell.hideButtonMore(false) - cell.hideLabelInfo(false) - cell.hideLabelSubinfo(false) - cell.hideImageStatus(false) - cell.fileTitleLabel?.font = UIFont.systemFont(ofSize: 15) - - if width < 120 { - cell.hideImageFavorite(true) - cell.hideImageLocal(true) - cell.fileTitleLabel?.font = UIFont.systemFont(ofSize: 10) - if width < 100 { - cell.hideImageItem(true) - cell.hideButtonMore(true) - cell.hideLabelInfo(true) - cell.hideLabelSubinfo(true) - cell.hideImageStatus(true) - } - } - } - // Hide buttons if metadata.name != global.appName { cell.titleInfoTrailingFull() @@ -612,21 +485,12 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { - func setContent(header: UICollectionReusableView, indexPath: IndexPath) { - let (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) = getHeaderHeight(section: indexPath.section) if kind == UICollectionView.elementKindSectionHeader { - if dataSource.getMetadataSourceForAllSections().isEmpty { - - guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionFirstHeaderEmptyData", for: indexPath) as? NCSectionFirstHeaderEmptyData else { return NCSectionFirstHeaderEmptyData() } - self.sectionFirstHeaderEmptyData = header - header.delegate = self - - } - if let header = header as? NCSectionFirstHeader { - let recommendations = self.database.getRecommendedFiles(account: self.session.account) - var sectionText = NSLocalizedString("_all_files_", comment: "") + if indexPath.section == 0 { + guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionHeaderMenu", for: indexPath) as? NCSectionHeaderMenu else { return UICollectionReusableView() } + let (_, heightHeaderRichWorkspace, heightHeaderSection) = getHeaderHeight(section: indexPath.section) if NCKeychain().getPersonalFilesOnly(account: session.account) { sectionText = NSLocalizedString("_personal_files_", comment: "") @@ -708,6 +572,8 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { self.sectionFirstHeader = header // setContent(header: header, indexPath: indexPath) +// self.headerMenu = header +// self.headerMenu?.setViewTransfer(isHidden: true) if layoutForView?.layout == NCGlobal.shared.layoutGrid { header.setImageSwitchList() header.buttonSwitch.accessibilityLabel = NSLocalizedString("_list_view_", comment: "") @@ -715,16 +581,19 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { header.setImageSwitchGrid() header.buttonSwitch.accessibilityLabel = NSLocalizedString("_grid_view_", comment: "") } - header.delegate = self - if !isSearchingMode, headerMenuTransferView, isHeaderMenuTransferViewEnabled() != nil { - header.setViewTransfer(isHidden: false) + header.delegate = self + + if !isSearchingMode, headerMenuTransferView, isHeaderMenuTransferViewEnabled() != nil, let ocId = NCNetworking.shared.transferInForegorund?.ocId { + let text = String(format: NSLocalizedString("_upload_foreground_msg_", comment: ""), NCBrandOptions.shared.brand) +// header.setViewTransfer(isHidden: false, text: text) + header.setViewTransfer(isHidden: false, ocId: ocId, text: text, progress: NCNetworking.shared.transferInForegorund?.progress) } else { header.setViewTransfer(isHidden: true) } if headerMenuButtonsView { - header.setStatusButtonsView(enable: !dataSource.getMetadataSourceForAllSections().isEmpty) + header.setStatusButtonsView(enable: !dataSource.isEmpty()) header.setButtonsView(height: NCGlobal.shared.heightButtonsView) header.setSortedTitle(layoutForView?.titleButtonHeader ?? "") } else { @@ -765,6 +634,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { footer.setTitleLabel("") footer.setButtonText(NSLocalizedString("_show_more_results_", comment: "")) + footer.buttonSection.setTitleColor(NCBrandColor.shared.customer, for: .normal) footer.separatorIsHidden(true) footer.buttonIsHidden(true) footer.hideActivityIndicatorSection() @@ -798,6 +668,14 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } } + func setContent(header: UICollectionReusableView, indexPath: IndexPath) { + if let header = header as? NCSectionHeader { + let text = self.dataSource.getSectionValueLocalization(indexPath: indexPath) + + header.setContent(text: text) + } + } + // MARK: - func getAvatarFromIconUrl(metadata: tableMetadata) -> String? { @@ -842,6 +720,98 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } } } + + // MARK: - Cancel (Download Upload) + + // sessionIdentifierDownload: String = "com.nextcloud.nextcloudkit.session.download" + // sessionIdentifierUpload: String = "com.nextcloud.nextcloudkit.session.upload" + + // sessionUploadBackground: String = "com.nextcloud.session.upload.background" + // sessionUploadBackgroundWWan: String = "com.nextcloud.session.upload.backgroundWWan" + // sessionUploadBackgroundExtension: String = "com.nextcloud.session.upload.extension" + + func cancelSession(metadata: tableMetadata) async { + + let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView) + utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId)) + NCManageDatabase.shared.deleteLocalFileOcId(metadata.ocId) + + // No session found + if metadata.session.isEmpty { + NCNetworking.shared.uploadRequest.removeValue(forKey: fileNameLocalPath) + NCNetworking.shared.downloadRequest.removeValue(forKey: fileNameLocalPath) + NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) + return + } + + // DOWNLOAD FOREGROUND + if metadata.session == NextcloudKit.shared.nkCommonInstance.identifierSessionDownload { + if let request = NCNetworking.shared.downloadRequest[fileNameLocalPath] { + request.cancel() + } else if let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) { + NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId, + session: "", + sessionError: "", + selector: "", + status: NCGlobal.shared.metadataStatusNormal) + NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadCancelFile), + object: nil, + userInfo: ["ocId": metadata.ocId, + "serverUrl": metadata.serverUrl, + "account": metadata.account]) + } + return + } + + // DOWNLOAD BACKGROUND + if metadata.session == NCNetworking.shared.sessionDownloadBackground { + let session: URLSession? = NCNetworking.shared.sessionManagerDownloadBackground + if let tasks = await session?.tasks { + for task in tasks.2 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) + if task.taskIdentifier == metadata.sessionTaskIdentifier { + task.cancel() + } + } + } + } + + // UPLOAD FOREGROUND + if metadata.session == NextcloudKit.shared.nkCommonInstance.identifierSessionUpload { + if let request = NCNetworking.shared.uploadRequest[fileNameLocalPath] { + request.cancel() + NCNetworking.shared.uploadRequest.removeValue(forKey: fileNameLocalPath) + } + NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) + NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile), + object: nil, + userInfo: ["ocId": metadata.ocId, + "serverUrl": metadata.serverUrl, + "account": metadata.account]) + return + } + + // UPLOAD BACKGROUND + var session: URLSession? + if metadata.session == NCNetworking.shared.sessionUploadBackground { + session = NCNetworking.shared.sessionManagerUploadBackground + } else if metadata.session == NCNetworking.shared.sessionUploadBackgroundWWan { + session = NCNetworking.shared.sessionManagerUploadBackgroundWWan + } + if let tasks = await session?.tasks { + for task in tasks.1 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) + if task.taskIdentifier == metadata.sessionTaskIdentifier { + task.cancel() + NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) + NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile), + object: nil, + userInfo: ["ocId": metadata.ocId, + "serverUrl": metadata.serverUrl, + "account": metadata.account]) + } + } + } + } func removeImageCache(metadatas: [tableMetadata]) { DispatchQueue.global().async { diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift index 141ef55c3b..2b97cde007 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift @@ -25,15 +25,15 @@ import UIKit import Foundation import NextcloudKit -extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { +extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate, NCSelectableNavigationView { + func selectAll() { - if !fileSelect.isEmpty, self.dataSource.getMetadatas().count == fileSelect.count { - fileSelect = [] - } else { - fileSelect = self.dataSource.getMetadatas().compactMap({ $0.ocId }) - } + fileSelect = selectableDataSource.compactMap({ $0.primaryKeyValue }) tabBarSelect?.update(fileSelect: fileSelect, metadatas: getSelectedMetadatas(), userId: session.userId) - self.collectionView.reloadData() + DispatchQueue.main.async { + self.collectionView.reloadData() + self.setNavigationRightItems(enableMenu: false) + } } func delete() { @@ -53,7 +53,7 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { }) } - alertController.addAction(UIAlertAction(title: NSLocalizedString("_remove_local_file_", comment: ""), style: .default) { (_: UIAlertAction) in + alertController.addAction(UIAlertAction(title: NSLocalizedString("_remove_local_file_", comment: ""), style: .default) { [self] (_: UIAlertAction) in let copyMetadatas = metadatas Task { @@ -104,7 +104,6 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { } await self.setEditMode(false) } - })) alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel)) self.present(alert, animated: true) @@ -151,10 +150,13 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { navigationItem.leftBarButtonItems = nil } else { ///Magentacloud branding changes hide user account button on left navigation bar -// setNavigationLeftItems() + setNavigationLeftItems() } - (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() + navigationController?.interactivePopGestureRecognizer?.isEnabled = !editMode + navigationItem.hidesBackButton = editMode + searchController(enabled: !editMode) + self.setNavigationRightItems(enableMenu: true) self.collectionView.reloadData() } @@ -171,10 +173,7 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { func toggleSelect(isOn: Bool? = nil) { DispatchQueue.main.async { self.isEditMode = isOn ?? !self.isEditMode - self.selectOcId.removeAll() - self.setNavigationLeftItems() - self.setNavigationRightItems() - self.collectionView.reloadData() + self.setEditMode(self.isEditMode) } await (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() @@ -187,11 +186,11 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { actions.append(.cancelAction { self.toggleSelect() }) - if selectOcId.count != dataSource.getMetadataSourceForAllSections().count { + if fileSelect.count != selectableDataSource.count { actions.append(.selectAllAction(action: selectAll)) } - guard !selectOcId.isEmpty else { return actions } + guard !fileSelect.isEmpty else { return actions } actions.append(.seperator(order: 0)) @@ -204,10 +203,10 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { var canOpenIn = false var isDirectoryE2EE = false - for ocId in selectOcId { + for ocId in fileSelect { guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { continue } if metadata.e2eEncrypted { - selectOcId.removeAll(where: {$0 == metadata.ocId}) + fileSelect.removeAll(where: {$0 == metadata.ocId}) } else { selectedMetadatas.append(metadata) } @@ -218,14 +217,14 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { if metadata.directory { isAnyFolder = true } if metadata.lock { isAnyLocked = true - if metadata.lockOwner != appDelegate.userId { + if metadata.lockOwner != session.userId { canUnlock = false } } guard !isAnyOffline else { continue } if metadata.directory, - let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", appDelegate.account, metadata.serverUrl + "/" + metadata.fileName)) { + let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", session.account, metadata.serverUrl + "/" + metadata.fileName)) { isAnyOffline = directory.offline } else if let localFile = NCManageDatabase.shared.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) { isAnyOffline = localFile.offline @@ -241,43 +240,15 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { } if canOpenIn { - actions.append(.share(selectedMetadatas: selectedMetadatas, viewController: self, completion: { self.toggleSelect() })) + actions.append(.openInAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: { self.toggleSelect() })) } - if !isAnyFolder, canUnlock, !NCGlobal.shared.capabilityFilesLockVersion.isEmpty { + if !isAnyFolder, canUnlock, !NCCapabilities.shared.getCapabilities(account: controller?.account).capabilityFilesLockVersion.isEmpty { actions.append(.lockUnlockFiles(shouldLock: !isAnyLocked, metadatas: selectedMetadatas, completion: { self.toggleSelect() })) } if !selectedMediaMetadatas.isEmpty { - var title: String = NSLocalizedString("_save_selected_files_", comment: "") - var icon = NCUtility().loadImage(named: "save_files",colors: [NCBrandColor.shared.iconImageColor]) - if selectedMediaMetadatas.allSatisfy({ NCManageDatabase.shared.getMetadataLivePhoto(metadata: $0) != nil }) { - title = NSLocalizedString("_livephoto_save_", comment: "") - icon = NCUtility().loadImage(named: "livephoto") - } - - actions.append(NCMenuAction( - title: title, - icon: icon, - order: 0, - action: { _ in - for metadata in selectedMediaMetadatas { - if let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) { - NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV, hudView: self.view)) - } else { - if NCUtilityFileSystem().fileProviderStorageExists(metadata) { - NCActionCenter.shared.saveAlbum(metadata: metadata, controller: self.tabBarController as? NCMainTabBarController) - } else { - if NCNetworking.shared.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty { - NCNetworking.shared.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum)) - } - } - } - } - self.toggleSelect() - } - ) - ) + actions.append(.saveMediaAction(selectedMediaMetadatas: selectedMediaMetadatas, controller: self.controller, completion: { self.toggleSelect() })) } actions.append(.setAvailableOfflineAction(selectedMetadatas: selectedMetadatas, isAnyOffline: isAnyOffline, viewController: self, completion: { self.reloadDataSource() @@ -292,4 +263,66 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { return actions } + func setNavigationRightItems(enableMenu: Bool = false) { + DispatchQueue.main.async { + if self.isEditMode { + let more = UIBarButtonItem(image: UIImage(systemName: "ellipsis"), style: .plain) { self.presentMenu(with: self.createMenuActions())} + self.navigationItem.rightBarButtonItems = [more] + } else { + let select = UIBarButtonItem(title: NSLocalizedString("_select_", comment: ""), style: UIBarButtonItem.Style.plain) { self.toggleSelect() } + let notification = UIBarButtonItem(image: UIImage(systemName: "bell"), style: .plain, action: self.tapNotification) + if self.layoutKey == NCGlobal.shared.layoutViewFiles { + self.navigationItem.rightBarButtonItems = [select, notification] + } else { + self.navigationItem.rightBarButtonItems = [select] + } + let transfer = UIBarButtonItem(image: UIImage(systemName: "arrow.left.arrow.right.circle.fill"), style: .plain, action: self.tapTransfer) + let resultsCount = self.database.getResultsMetadatas(predicate: NSPredicate(format: "status != %i", NCGlobal.shared.metadataStatusNormal))?.count ?? 0 + + if self.layoutKey == NCGlobal.shared.layoutViewFiles { + self.navigationItem.rightBarButtonItems = resultsCount > 0 ? [select, notification, transfer] : [select, notification] + } else { + self.navigationItem.rightBarButtonItems = [select] + } + } + guard self.layoutKey == NCGlobal.shared.layoutViewFiles else { return } + self.navigationItem.title = self.titleCurrentFolder + } + } + + func onListSelected() { + if layoutForView?.layout == NCGlobal.shared.layoutGrid { + headerMenu?.buttonSwitch.accessibilityLabel = NSLocalizedString("_grid_view_", comment: "") + layoutForView?.layout = NCGlobal.shared.layoutList + NCManageDatabase.shared.setLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl, layout: layoutForView?.layout) + self.groupByField = "name" + if self.dataSource.groupByField != self.groupByField { + self.dataSource.changeGroupByField(self.groupByField) + } + self.saveLayout(layoutForView!) + self.collectionView.reloadData() + self.collectionView.collectionViewLayout.invalidateLayout() + self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) {_ in self.isTransitioning = false } + } + } + + func onGridSelected() { + if layoutForView?.layout == NCGlobal.shared.layoutList { + headerMenu?.buttonSwitch.accessibilityLabel = NSLocalizedString("_list_view_", comment: "") + layoutForView?.layout = NCGlobal.shared.layoutGrid + NCManageDatabase.shared.setLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl, layout: layoutForView?.layout) + if isSearchingMode { + self.groupByField = "name" + } else { + self.groupByField = "classFile" + } + if self.dataSource.groupByField != self.groupByField { + self.dataSource.changeGroupByField(self.groupByField) + } + self.saveLayout(layoutForView!) + self.collectionView.reloadData() + self.collectionView.collectionViewLayout.invalidateLayout() + self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) {_ in self.isTransitioning = false } + } + } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index fbc1330258..c7b6af690a 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -28,7 +28,8 @@ import NextcloudKit import EasyTipView import LucidBanner -class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionFirstHeaderDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { +//class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionFirstHeaderDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { +class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionHeaderMenuDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate, NCEmptyDataSetDelegate { @IBOutlet weak var collectionView: UICollectionView! @@ -70,21 +71,24 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var attributesZoomIn: UIMenuElement.Attributes = [] var attributesZoomOut: UIMenuElement.Attributes = [] let maxImageGrid: CGFloat = 7 - var headerMenu: NCSectionFirstHeader? - var tipViewAccounts: EasyTipView? var syncMetadatasTask: Task? + var tipViewAutoUpload: EasyTipView? + var headerMenu: NCSectionHeaderMenu? + var headerMenuTransferView = false + var headerMenuButtonsView: Bool = true + var headerRichWorkspaceDisable: Bool = false + // DECLARE var layoutKey = "" var titleCurrentFolder = "" var titlePreviusFolder: String? var enableSearchBar: Bool = false - var headerRichWorkspaceDisable: Bool = false + var groupByField = "name" var emptyImageName: String? var emptyImageColors: [UIColor]? - var headerMenuButtonsView: Bool = true var emptyImage: UIImage? var emptyTitle: String = "" @@ -107,6 +111,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let heightHeaderRecommendations: CGFloat = 160 let heightHeaderSection: CGFloat = 30 + var isTransitioning: Bool = false + var selectableDataSource: [RealmSwiftObject] { dataSource.getMetadataSourceForAllSections() } + var pushed: Bool = false + var emptyDataSet: NCEmptyDataSet? + var session: NCSession.Session { NCSession.shared.getSession(controller: tabBarController) } @@ -192,8 +201,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS view.backgroundColor = .systemBackground collectionView.backgroundColor = .systemBackground - refreshControl.tintColor = .clear - + refreshControl.tintColor = .gray + + listLayout = NCListLayout() + gridLayout = NCGridLayout() + if enableSearchBar { searchController = UISearchController(searchResultsController: nil) searchController?.searchResultsUpdater = self @@ -202,7 +214,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS searchController?.searchBar.delegate = self searchController?.searchBar.autocapitalizationType = .none navigationItem.searchController = searchController - navigationItem.hidesSearchBarWhenScrolling = true + navigationItem.hidesSearchBarWhenScrolling = false + navigationItem.backBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_back_", comment: ""), style: .plain, target: nil, action: nil) } // Cell @@ -212,12 +225,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS collectionView.register(UINib(nibName: "NCTransferCell", bundle: nil), forCellWithReuseIdentifier: "transferCell") // Header - collectionView.register(UINib(nibName: "NCSectionFirstHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionFirstHeader") - collectionView.register(UINib(nibName: "NCSectionFirstHeader", bundle: nil), forSupplementaryViewOfKind: mediaSectionHeader, withReuseIdentifier: "sectionFirstHeader") + collectionView.register(UINib(nibName: "NCSectionHeaderMenu", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionHeaderMenu") collectionView.register(UINib(nibName: "NCSectionHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionHeader") - collectionView.register(UINib(nibName: "NCSectionHeader", bundle: nil), forSupplementaryViewOfKind: mediaSectionHeader, withReuseIdentifier: "sectionHeader") - collectionView.register(UINib(nibName: "NCSectionFirstHeaderEmptyData", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionFirstHeaderEmptyData") - collectionView.register(UINib(nibName: "NCSectionFirstHeaderEmptyData", bundle: nil), forSupplementaryViewOfKind: mediaSectionHeader, withReuseIdentifier: "sectionFirstHeaderEmptyData") // Footer collectionView.register(UINib(nibName: "NCSectionFooter", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "sectionFooter") @@ -234,6 +243,9 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } + // Empty + emptyDataSet = NCEmptyDataSet(view: collectionView, offset: getHeaderHeight(), delegate: self) + let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPressCollecationView(_:))) longPressedGesture.minimumPressDuration = 0.5 longPressedGesture.delegate = self @@ -255,6 +267,15 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS guard let self else { return } self.collectionView.reloadData() } + if(!UserDefaults.standard.bool(forKey: "isInitialPrivacySettingsShowed") || isApplicationUpdated()){ + redirectToPrivacyViewController() + + //set current app version + let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String + UserDefaults.standard.set(appVersion, forKey: "CurrentAppVersion") + } + +// NotificationCenter.default.addObserver(self, selector: #selector(changeTheming(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeTheming), object: nil) DispatchQueue.main.async { self.collectionView?.collectionViewLayout.invalidateLayout() @@ -268,7 +289,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS navigationController?.navigationBar.topItem?.title = titlePreviusFolder } navigationItem.title = titleCurrentFolder - + navigationController?.setNavigationBarAppearance() + navigationController?.navigationBar.prefersLargeTitles = true + navigationController?.setNavigationBarHidden(false, animated: true) + + appDelegate.activeViewController = self isEditMode = false /// Magentacloud branding changes hide user account button on left navigation bar @@ -276,6 +301,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS setNavigationRightItems() layoutForView = database.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) + gridLayout.column = CGFloat(layoutForView?.columnGrid ?? 3) if isLayoutList { collectionView?.collectionViewLayout = listLayout self.layoutType = global.layoutList @@ -300,6 +326,30 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(closeRichWorkspaceWebView), name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(changeStatusFolderE2EE(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeStatusFolderE2EE), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(changeLayout(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeLayout), object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDeleteFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(copyMoveFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterCopyMoveFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(renameFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterRenameFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(createFolder(_:)), name: NSNotification.Name(rawValue: global.notificationCenterCreateFolder), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(favoriteFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterFavoriteFile), object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(downloadStartFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDownloadStartFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(downloadedFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDownloadedFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(downloadCancelFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDownloadCancelFile), object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(uploadStartFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadStartFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadedFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(uploadedLivePhoto(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadedLivePhoto), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(uploadCancelFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadCancelFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(updateShare(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUpdateShare), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(triggerProgressTask(_:)), name: NSNotification.Name(rawValue: global.notificationCenterProgressTask), object: nil) + + // FIXME: iPAD PDF landscape mode iOS 16 + DispatchQueue.main.async { + self.collectionView?.collectionViewLayout.invalidateLayout() + } } override func viewWillDisappear(_ animated: Bool) { @@ -307,7 +357,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.networking.cancelUnifiedSearchFiles() dismissTip() - + pushed = false + toggleSelect(isOn: false) // Cancel Queue & Retrieves Properties self.networking.downloadThumbnailQueue.cancelAll() self.networking.unifiedSearchQueue.cancelAll() @@ -324,6 +375,19 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS removeImageCache(metadatas: self.dataSource.getMetadatas()) } + + func isApplicationUpdated() -> Bool { + let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String ?? "" + let currentVersion = UserDefaults.standard.string(forKey: "CurrentAppVersion") + return currentVersion != appVersion + } + + func redirectToPrivacyViewController() { + let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) + let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as! UINavigationController + newViewController.modalPresentationStyle = .fullScreen + self.present(newViewController, animated: true, completion: nil) + } func presentationControllerDidDismiss( _ presentationController: UIPresentationController) { let viewController = presentationController.presentedViewController @@ -475,7 +539,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let error = userInfo["error"] as? NKError, error.errorCode != global.errorNotModified else { return } /// Magentacloud branding changes hide user account button on left navigation bar -// setNavigationLeftItems() + setNavigationLeftItems() } @objc func changeTheming(_ notification: NSNotification) { @@ -518,7 +582,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.collectionView.collectionViewLayout.invalidateLayout() - (self.navigationController as? NCMainNavigationController)?.updateRightMenu() +// (self.navigationController as? NCMainNavigationController)?.updateRightMenu() } @objc func reloadDataSource(_ notification: NSNotification) { @@ -632,6 +696,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS NCContentPresenter().showError(error: error) } reloadDataSource() + } else { + collectionView.reloadData() } } @@ -719,7 +785,25 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } @objc func uploadStartFile(_ notification: NSNotification) { - collectionView?.reloadData() + guard let userInfo = notification.userInfo as NSDictionary?, + let ocId = userInfo["ocId"] as? String, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String, + !isSearchingMode, + let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) + else { return } + + // Header view trasfer + if metadata.isTransferInForeground { + NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: 0) + DispatchQueue.main.async { self.collectionView?.reloadData() } + } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } } @objc func uploadedFile(_ notification: NSNotification) { @@ -760,295 +844,79 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS collectionView?.reloadData() } } + + @objc func triggerProgressTask(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let progressNumber = userInfo["progress"] as? NSNumber, + let totalBytes = userInfo["totalBytes"] as? Int64, + let totalBytesExpected = userInfo["totalBytesExpected"] as? Int64, + let ocId = userInfo["ocId"] as? String, + let ocIdTransfer = userInfo["ocIdTransfer"] as? String, + let session = userInfo["session"] as? String + else { return } - @objc func updateShare(_ notification: NSNotification) { - if isSearchingMode { - networkSearch() - } else { - self.dataSource.removeAll() - getServerData() - } - } - - // MARK: - Layout + let chunk: Int = userInfo["chunk"] as? Int ?? 0 + let e2eEncrypted: Bool = userInfo["e2eEncrypted"] as? Bool ?? false - func setNavigationLeftItems() { - guard layoutKey == global.layoutViewFiles, - let tableAccount = database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) else { - return } - let image = utility.loadUserImage(for: tableAccount.user, displayName: tableAccount.displayName, urlBase: tableAccount.urlBase) - let accountButton = AccountSwitcherButton(type: .custom) - let accounts = database.getAllAccountOrderAlias() - var childrenAccountSubmenu: [UIMenuElement] = [] - - accountButton.setImage(image, for: .normal) - accountButton.setImage(image, for: .highlighted) - accountButton.semanticContentAttribute = .forceLeftToRight - accountButton.sizeToFit() - - if !accounts.isEmpty { - let accountActions: [UIAction] = accounts.map { account in - let image = utility.loadUserImage(for: account.user, displayName: account.displayName, urlBase: account.urlBase) - var name: String = "" - var url: String = "" - - if account.alias.isEmpty { - name = account.displayName - url = (URL(string: account.urlBase)?.host ?? "") + let transfer = NCTransferProgress.shared.append(NCTransferProgress.Transfer(ocId: ocId, ocIdTransfer: ocIdTransfer, session: session, chunk: chunk, e2eEncrypted: e2eEncrypted, progressNumber: progressNumber, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected)) + + DispatchQueue.main.async { + if self.headerMenuTransferView && (chunk > 0 || e2eEncrypted) { + if NCNetworking.shared.transferInForegorund?.ocId == ocId { + NCNetworking.shared.transferInForegorund?.progress = progressNumber.floatValue } else { - name = account.alias + NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: progressNumber.floatValue) + self.collectionView.reloadData() } - - let action = UIAction(title: name, image: image, state: account.active ? .on : .off) { _ in - if !account.active { - NCAccount().changeAccount(account.account, userProfile: nil, controller: self.controller) { } - self.setEditMode(false) + self.headerMenu?.progressTransfer.progress = transfer.progressNumber.floatValue + } else { + guard let indexPath = self.dataSource.getIndexPathMetadata(ocId: ocId).indexPath, + let cell = self.collectionView?.cellForItem(at: indexPath), + let cell = cell as? NCCellProtocol else { return } + if progressNumber.floatValue == 1 && !(cell is NCTransferCell) { + cell.fileProgressView?.isHidden = true + cell.fileProgressView?.progress = .zero + cell.setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCImageCache.images.buttonMore) + if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) { + cell.writeInfoDateSize(date: metadata.date, size: metadata.size) + } else { + cell.fileInfoLabel?.text = "" + cell.fileSubinfoLabel?.text = "" + } + } else { + cell.fileProgressView?.isHidden = false + cell.fileProgressView?.progress = progressNumber.floatValue + cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCImageCache.images.buttonStop) + let status = userInfo["status"] as? Int ?? NCGlobal.shared.metadataStatusNormal + if status == NCGlobal.shared.metadataStatusDownloading { + cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↓ " + self.utilityFileSystem.transformedSize(totalBytes) + } else if status == NCGlobal.shared.metadataStatusUploading { + if totalBytes > 0 { + cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ " + self.utilityFileSystem.transformedSize(totalBytes) + } else { + cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ …" + } } } - - action.subtitle = url - return action - } - - let addAccountAction = UIAction(title: NSLocalizedString("_add_account_", comment: ""), image: utility.loadImage(named: "person.crop.circle.badge.plus", colors: NCBrandColor.shared.iconImageMultiColors)) { _ in - self.appDelegate.openLogin(selector: self.global.introLogin) - } - - let settingsAccountAction = UIAction(title: NSLocalizedString("_account_settings_", comment: ""), image: utility.loadImage(named: "gear", colors: [NCBrandColor.shared.iconImageColor])) { _ in - let accountSettingsModel = NCAccountSettingsModel(controller: self.controller, delegate: self) - let accountSettingsView = NCAccountSettingsView(model: accountSettingsModel) - let accountSettingsController = UIHostingController(rootView: accountSettingsView) - self.present(accountSettingsController, animated: true, completion: nil) - } - - if !NCBrandOptions.shared.disable_multiaccount { - childrenAccountSubmenu.append(addAccountAction) - } - childrenAccountSubmenu.append(settingsAccountAction) - - let addAccountSubmenu = UIMenu(title: "", options: .displayInline, children: childrenAccountSubmenu) - let menu = UIMenu(children: accountActions + [addAccountSubmenu]) - - accountButton.menu = menu - accountButton.showsMenuAsPrimaryAction = true - - accountButton.onMenuOpened = { - self.dismissTip() } } + } - navigationItem.leftItemsSupplementBackButton = true - navigationItem.setLeftBarButtonItems([UIBarButtonItem(customView: accountButton)], animated: true) - - if titlePreviusFolder != nil { - navigationController?.navigationBar.topItem?.title = titlePreviusFolder + @objc func updateShare(_ notification: NSNotification) { + if isSearchingMode { + networkSearch() + } else { + self.dataSource.removeAll() + getServerData() } - - navigationItem.title = titleCurrentFolder } - func setNavigationRightItems() { - guard layoutKey != global.layoutViewTransfers else { return } - let isTabBarHidden = self.tabBarController?.tabBar.isHidden ?? true - let isTabBarSelectHidden = tabBarSelect.isHidden() - - func createMenuActions() -> [UIMenuElement] { - guard let layoutForView = database.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) else { return [] } - - let select = UIAction(title: NSLocalizedString("_select_", comment: ""), - image: utility.loadImage(named: "checkmark.circle"), - attributes: (self.dataSource.isEmpty() || NCNetworking.shared.isOffline) ? .disabled : []) { _ in - self.setEditMode(true) - self.collectionView.reloadData() - } - - let list = UIAction(title: NSLocalizedString("_list_", comment: ""), image: utility.loadImage(named: "list.bullet"), state: layoutForView.layout == global.layoutList ? .on : .off) { _ in - - layoutForView.layout = self.global.layoutList - - NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, - object: nil, - userInfo: ["account": self.session.account, - "serverUrl": self.serverUrl, - "layoutForView": layoutForView]) - } - - let grid = UIAction(title: NSLocalizedString("_icons_", comment: ""), image: utility.loadImage(named: "square.grid.2x2"), state: layoutForView.layout == global.layoutGrid ? .on : .off) { _ in - - layoutForView.layout = self.global.layoutGrid - - NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, - object: nil, - userInfo: ["account": self.session.account, - "serverUrl": self.serverUrl, - "layoutForView": layoutForView]) - } - - let mediaSquare = UIAction(title: NSLocalizedString("_media_square_", comment: ""), image: utility.loadImage(named: "square.grid.3x3"), state: layoutForView.layout == global.layoutPhotoSquare ? .on : .off) { _ in - - layoutForView.layout = self.global.layoutPhotoSquare - - NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, - object: nil, - userInfo: ["account": self.session.account, - "serverUrl": self.serverUrl, - "layoutForView": layoutForView]) - } - - let mediaRatio = UIAction(title: NSLocalizedString("_media_ratio_", comment: ""), image: utility.loadImage(named: "rectangle.grid.3x2"), state: layoutForView.layout == self.global.layoutPhotoRatio ? .on : .off) { _ in - - layoutForView.layout = self.global.layoutPhotoRatio - - NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, - object: nil, - userInfo: ["account": self.session.account, - "serverUrl": self.serverUrl, - "layoutForView": layoutForView]) - } - - let viewStyleSubmenu = UIMenu(title: "", options: .displayInline, children: [list, grid, mediaSquare, mediaRatio]) - - let ascending = layoutForView.ascending - let ascendingChevronImage = utility.loadImage(named: ascending ? "chevron.up" : "chevron.down") - let isName = layoutForView.sort == "fileName" - let isDate = layoutForView.sort == "date" - let isSize = layoutForView.sort == "size" - - let byName = UIAction(title: NSLocalizedString("_name_", comment: ""), image: isName ? ascendingChevronImage : nil, state: isName ? .on : .off) { _ in - - if isName { // repeated press - layoutForView.ascending = !layoutForView.ascending - } - layoutForView.sort = "fileName" - - NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, - object: nil, - userInfo: ["account": self.session.account, - "serverUrl": self.serverUrl, - "layoutForView": layoutForView]) - } - - let byNewest = UIAction(title: NSLocalizedString("_date_", comment: ""), image: isDate ? ascendingChevronImage : nil, state: isDate ? .on : .off) { _ in - - if isDate { // repeated press - layoutForView.ascending = !layoutForView.ascending - } - layoutForView.sort = "date" - - NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, - object: nil, - userInfo: ["account": self.session.account, - "serverUrl": self.serverUrl, - "layoutForView": layoutForView]) - } - - let byLargest = UIAction(title: NSLocalizedString("_size_", comment: ""), image: isSize ? ascendingChevronImage : nil, state: isSize ? .on : .off) { _ in - - if isSize { // repeated press - layoutForView.ascending = !layoutForView.ascending - } - layoutForView.sort = "size" - - NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, - object: nil, - userInfo: ["account": self.session.account, - "serverUrl": self.serverUrl, - "layoutForView": layoutForView]) - } - - let sortSubmenu = UIMenu(title: NSLocalizedString("_order_by_", comment: ""), options: .displayInline, children: [byName, byNewest, byLargest]) - - let foldersOnTop = UIAction(title: NSLocalizedString("_directory_on_top_no_", comment: ""), image: utility.loadImage(named: "folder"), state: layoutForView.directoryOnTop ? .on : .off) { _ in - - layoutForView.directoryOnTop = !layoutForView.directoryOnTop - - NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeLayout, - object: nil, - userInfo: ["account": self.session.account, - "serverUrl": self.serverUrl, - "layoutForView": layoutForView]) - } - - let personalFilesOnly = NCKeychain().getPersonalFilesOnly(account: session.account) - let personalFilesOnlyAction = UIAction(title: NSLocalizedString("_personal_files_only_", comment: ""), image: utility.loadImage(named: "folder.badge.person.crop", colors: NCBrandColor.shared.iconImageMultiColors), state: personalFilesOnly ? .on : .off) { _ in - - NCKeychain().setPersonalFilesOnly(account: self.session.account, value: !personalFilesOnly) - - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource, userInfo: ["serverUrl": self.serverUrl, "clearDataSource": true]) - self.setNavigationRightItems() - } - - let showDescriptionKeychain = NCKeychain().showDescription - let showDescription = UIAction(title: NSLocalizedString("_show_description_", comment: ""), image: utility.loadImage(named: "list.dash.header.rectangle"), attributes: richWorkspaceText == nil ? .disabled : [], state: showDescriptionKeychain && richWorkspaceText != nil ? .on : .off) { _ in - - NCKeychain().showDescription = !showDescriptionKeychain - - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource, userInfo: ["serverUrl": self.serverUrl, "clearDataSource": true]) - self.setNavigationRightItems() - } - showDescription.subtitle = richWorkspaceText == nil ? NSLocalizedString("_no_description_available_", comment: "") : "" - - if layoutKey == global.layoutViewRecent { - return [select] - } else { - var additionalSubmenu = UIMenu() - if layoutKey == global.layoutViewFiles { - additionalSubmenu = UIMenu(title: "", options: .displayInline, children: [foldersOnTop, personalFilesOnlyAction, showDescription]) - } else { - additionalSubmenu = UIMenu(title: "", options: .displayInline, children: [foldersOnTop, showDescription]) - } - return [select, viewStyleSubmenu, sortSubmenu, additionalSubmenu] - } - } + // MARK: - Layout - if isEditMode { - /// Magentacloud branding changes hide options on bottom tab bar -// tabBarSelect.update(selectOcId: selectOcId, metadatas: getSelectedMetadatas(), userId: appDelegate.userId) -// tabBarSelect.show() - let select = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: .done) { - self.setEditMode(false) - self.collectionView.reloadData() - } - navigationItem.rightBarButtonItems = [select] - } else if navigationItem.rightBarButtonItems == nil || (!isEditMode && !tabBarSelect.isHidden()) { - /// Magentacloud branding changes hide options on bottom tab bar -// tabBarSelect.hide() - let menuButton = UIBarButtonItem(image: utility.loadImage(named: "ellipsis.circle"), menu: UIMenu(children: createMenuActions())) - menuButton.tintColor = NCBrandColor.shared.iconImageColor - if layoutKey == global.layoutViewFiles { - let notification = UIBarButtonItem(image: utility.loadImage(named: "bell"), style: .plain) { - if let viewController = UIStoryboard(name: "NCNotification", bundle: nil).instantiateInitialViewController() as? NCNotification { - viewController.session = self.session - self.navigationController?.pushViewController(viewController, animated: true) - } - } - notification.tintColor = NCBrandColor.shared.iconImageColor - navigationItem.rightBarButtonItems = [menuButton, notification] - } else { - navigationItem.rightBarButtonItems = [menuButton] - } - } else { - navigationItem.rightBarButtonItems?.first?.menu = navigationItem.rightBarButtonItems?.first?.menu?.replacingChildren(createMenuActions()) - } - - if isEditMode { - let more = UIBarButtonItem(image: UIImage(systemName: "ellipsis"), style: .plain) { self.presentMenu(with: self.createMenuActions())} - navigationItem.rightBarButtonItems = [more] - } else { - let select = UIBarButtonItem(title: NSLocalizedString("_select_", comment: ""), style: UIBarButtonItem.Style.plain) { self.toggleSelect() } - if layoutKey == NCGlobal.shared.layoutViewFiles { - let notification = UIBarButtonItem(image: utility.loadImage(named: "bell"), style: .plain) { - if let viewController = UIStoryboard(name: "NCNotification", bundle: nil).instantiateInitialViewController() as? NCNotification { - self.navigationController?.pushViewController(viewController, animated: true) - } - } - notification.tintColor = NCBrandColor.shared.iconImageColor - navigationItem.rightBarButtonItems = [select, notification] - } else { - navigationItem.rightBarButtonItems = [select] - } - } - guard layoutKey == NCGlobal.shared.layoutViewFiles else { return } + func setNavigationLeftItems() { navigationItem.title = titleCurrentFolder } @@ -1101,6 +969,36 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.navigationItem.title = self.titleCurrentFolder } + // MARK: - Empty + + func emptyDataSetView(_ view: NCEmptyView) { + + self.emptyDataSet?.setOffset(getHeaderHeight()) + if isSearchingMode { + view.emptyImage.image = UIImage(named: "search")?.image(color: .gray, size: UIScreen.main.bounds.width) + if self.dataSourceTask?.state == .running { + view.emptyTitle.text = NSLocalizedString("_search_in_progress_", comment: "") + } else { + view.emptyTitle.text = NSLocalizedString("_search_no_record_found_", comment: "") + } + view.emptyDescription.text = NSLocalizedString("_search_instruction_", comment: "") + } else if self.dataSourceTask?.state == .running { + view.emptyImage.image = UIImage(named: "networkInProgress")?.image(color: .gray, size: UIScreen.main.bounds.width) + view.emptyTitle.text = NSLocalizedString("_request_in_progress_", comment: "") + view.emptyDescription.text = "" + } else { + if serverUrl.isEmpty { + view.emptyImage.image = emptyImage + view.emptyTitle.text = NSLocalizedString(emptyTitle, comment: "") + view.emptyDescription.text = NSLocalizedString(emptyDescription, comment: "") + } else { + view.emptyImage.image = UIImage(named: "folder_nmcloud") + view.emptyTitle.text = NSLocalizedString("_files_no_files_", comment: "") + view.emptyDescription.text = NSLocalizedString("_no_file_pull_down_", comment: "") + } + } + } + // MARK: - SEARCH func searchController(enabled: Bool) { @@ -1110,7 +1008,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS searchController?.searchBar.alpha = 1 } else { searchController?.searchBar.alpha = 0.3 - } } @@ -1153,23 +1050,34 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - TAP EVENT - func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { - tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + func tapMoreListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: image, sender: sender) } - func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { - tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: image, sender: sender) } func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) { + if isEditMode { return } guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) +// NCDownloadAction.shared.openShare(viewController: self, metadata: metadata, page: .sharing) + TealiumHelper.shared.trackEvent(title: "magentacloud-app.filebrowser.sharing", data: ["": ""]) + appDelegate.adjust.trackEvent(TriggerEvent(Sharing.rawValue)) } - func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + func tapMoreGridItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { + if isEditMode { return } guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } - toggleMenu(metadata: metadata, image: image, sender: sender) + if namedButtonMore == NCGlobal.shared.buttonMoreMore || namedButtonMore == NCGlobal.shared.buttonMoreLock { + toggleMenu(metadata: metadata, image: image) + } else if namedButtonMore == NCGlobal.shared.buttonMoreStop { + Task { + await cancelSession(metadata: metadata) + } + } } func tapRichWorkspace(_ sender: Any) { @@ -1197,13 +1105,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS didSelectMetadata(metadata, withOcIds: false) } - func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func tapButtonSwitch(_ sender: Any) { guard !isTransitioning else { return } isTransitioning = true - guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: appDelegate.account, key: layoutKey, serverUrl: serverUrl) else { return } + guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) else { return } if layoutForView.layout == NCGlobal.shared.layoutGrid { layoutForView.layout = NCGlobal.shared.layoutList @@ -1218,39 +1124,24 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS func tapButtonOrder(_ sender: Any) { let sortMenu = NCSortMenu() - sortMenu.toggleMenu(viewController: self, account: appDelegate.account, key: layoutKey, sortButton: sender as? UIButton, serverUrl: serverUrl) - } - - func tapButtonSwitch(_ sender: Any) { - guard !isTransitioning else { return } - isTransitioning = true - - guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: appDelegate.account, key: layoutKey, serverUrl: serverUrl) else { return } - - if layoutForView.layout == NCGlobal.shared.layoutGrid { - layoutForView.layout = NCGlobal.shared.layoutList - } else { - layoutForView.layout = NCGlobal.shared.layoutGrid - } - self.layoutForView = NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView) - self.collectionView.reloadData() - self.collectionView.collectionViewLayout.invalidateLayout() - self.collectionView.setCollectionViewLayout(layoutForView.layout == NCGlobal.shared.layoutList ? self.listLayout : self.gridLayout, animated: true) {_ in self.isTransitioning = false } + sortMenu.toggleMenu(viewController: self, account: session.account, key: layoutKey, sortButton: sender as? UIButton, serverUrl: serverUrl) } - func tapButtonOrder(_ sender: Any) { - let sortMenu = NCSortMenu() - sortMenu.toggleMenu(viewController: self, account: appDelegate.account, key: layoutKey, sortButton: sender as? UIButton, serverUrl: serverUrl) - } + func tapButtonTransfer(_ sender: Any) { } + + func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { } + + func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func longPressListItem(with objectId: String, indexPath: IndexPath, gestureRecognizer: UILongPressGestureRecognizer) { - } + func longPressListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func longPressMoreListItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func longPressMoreGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func longPressGridItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressMoreGridItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) @@ -1375,7 +1266,21 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - DataSource - func reloadDataSource() async { + @objc func reloadDataSource() async { + + // get auto upload folder + autoUploadFileName = NCManageDatabase.shared.getAccountAutoUploadFileName() + autoUploadDirectory = NCManageDatabase.shared.getAccountAutoUploadDirectory(urlBase: session.urlBase, userId: session.userId, account: session.account) + + // get layout for view + layoutForView = NCManageDatabase.shared.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) + + // set GroupField for Grid + if !isSearchingMode && layoutForView?.layout == NCGlobal.shared.layoutGrid { + groupByField = "classFile" + } else { + groupByField = "name" + } if isSearchingMode { isDirectoryEncrypted = false } else { @@ -1395,6 +1300,9 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS completion: nil) (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + self.refreshControl.endRefreshing() + self.collectionView.reloadData() + self.setNavigationRightItems() } } @@ -1518,46 +1426,24 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } } + + func pushViewController(viewController: UIViewController) { + if pushed { return } - // MARK: - Header size - - func getHeaderHeight(section: Int) -> (heightHeaderCommands: CGFloat, heightHeaderRichWorkspace: CGFloat, heightHeaderSection: CGFloat) { - var headerRichWorkspace: CGFloat = 0 - - func getHeaderHeight() -> CGFloat { - var size: CGFloat = 0 - - if isHeaderMenuTransferViewEnabled() != nil { - if !isSearchingMode { - size += global.heightHeaderTransfer - } - } - if headerMenuButtonsView { - size += NCGlobal.shared.heightButtonsView - } - if headerMenuButtonsView { - size += NCGlobal.shared.heightButtonsView - } - return size - } + pushed = true + navigationController?.pushViewController(viewController, animated: true) + } -// if isRecommendationActived, -// !isSearchingMode, -// NCKeychain().showRecommendedFiles, -// !self.database.getRecommendedFiles(account: self.session.account).isEmpty { -// heightHeaderRecommendations = self.heightHeaderRecommendations -// heightHeaderSection = self.heightHeaderSection -// } + // MARK: - Header size - if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { - if section == 0 { - return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) - } else { - return (0, 0, self.heightHeaderSection) - } - } else { - return (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) + func isHeaderMenuTransferViewEnabled() -> [tableMetadata]? { + if headerMenuTransferView, + NCNetworking.shared.isOnline, + let results = database.getResultsMetadatas(predicate: NSPredicate(format: "status IN %@", [global.metadataStatusWaitUpload, global.metadataStatusUploading])), + !results.isEmpty { + return Array(results) } + return nil } func sizeForHeaderInSection(section: Int) -> CGSize { @@ -1566,12 +1452,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let isIphone = UIDevice.current.userInterfaceIdiom == .phone if self.dataSource.isEmpty() { - height = utility.getHeightHeaderEmptyData(view: view, portraitOffset: emptyDataPortaitOffset, landscapeOffset: emptyDataLandscapeOffset) + height = utility.getHeightHeaderEmptyData(view: view, portraitOffset: emptyDataPortaitOffset, landscapeOffset: emptyDataLandscapeOffset, isHeaderMenuTransferViewEnabled: isHeaderMenuTransferViewEnabled() != nil) } else if isEditMode || (isLandscape && isIphone) { return CGSize.zero } else { - let (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) = getHeaderHeight(section: section) - height = heightHeaderRichWorkspace + heightHeaderRecommendations + heightHeaderSection + let (heightHeaderCommands, heightHeaderRichWorkspace, heightHeaderSection) = getHeaderHeight(section: section) + height = heightHeaderCommands + heightHeaderRichWorkspace + heightHeaderSection } return CGSize(width: collectionView.frame.width, height: height) diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index b0f882de81..558b0bad53 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -28,6 +28,7 @@ import NextcloudKit protocol NCSectionFirstHeaderDelegate: AnyObject { func tapButtonSwitch(_ sender: Any) func tapButtonOrder(_ sender: Any) + func tapButtonMore(_ sender: Any) func tapButtonTransfer(_ sender: Any) func tapRichWorkspace(_ sender: Any) func tapRecommendations(with metadata: tableMetadata) @@ -37,12 +38,14 @@ protocol NCSectionFirstHeaderDelegate: AnyObject { extension NCSectionFirstHeaderDelegate { func tapButtonSwitch(_ sender: Any) {} func tapButtonOrder(_ sender: Any) {} + func tapButtonMore(_ sender: Any) {} } class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegate { @IBOutlet weak var buttonSwitch: UIButton! @IBOutlet weak var buttonOrder: UIButton! + @IBOutlet weak var buttonMore: UIButton! @IBOutlet weak var buttonTransfer: UIButton! @IBOutlet weak var imageButtonTransfer: UIImageView! @IBOutlet weak var labelTransfer: UILabel! @@ -63,11 +66,10 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat @IBOutlet weak var viewRichWorkspaceHeightConstraint: NSLayoutConstraint! @IBOutlet weak var viewRecommendationsHeightConstraint: NSLayoutConstraint! @IBOutlet weak var viewSectionHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var transferSeparatorBottomHeightConstraint: NSLayoutConstraint! - @IBOutlet weak var textViewRichWorkspace: UITextView! @IBOutlet weak var collectionViewRecommendations: UICollectionView! @IBOutlet weak var labelRecommendations: UILabel! - @IBOutlet weak var labelSection: UILabel! private weak var delegate: NCSectionFirstHeaderDelegate? private let utility = NCUtility() @@ -91,10 +93,11 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat backgroundColor = .clear //Button - buttonSwitch.setImage(UIImage(systemName: "list.bullet"), for: .normal)//!.image(color: NCBrandColor.shared.iconColor, size: 25), for: .normal) + buttonSwitch.setImage(UIImage(systemName: "list.bullet")!.image(color: NCBrandColor.shared.iconColor, size: 25), for: .normal) buttonOrder.setTitle("", for: .normal) buttonOrder.setTitleColor(NCBrandColor.shared.brand, for: .normal) + buttonMore.setImage(UIImage(named: "more")!.image(color: NCBrandColor.shared.iconColor, size: 25), for: .normal) // Gradient // gradient.startPoint = CGPoint(x: 0, y: 0.8) @@ -104,7 +107,9 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat let tap = UITapGestureRecognizer(target: self, action: #selector(touchUpInsideViewRichWorkspace(_:))) tap.delegate = self viewRichWorkspace?.addGestureRecognizer(tap) - + viewSeparatorHeightConstraint.constant = 0.5 + viewSeparator.backgroundColor = .separator + markdownParser = MarkdownParser(font: UIFont.systemFont(ofSize: 15), color: NCBrandColor.shared.textColor) markdownParser.header.font = UIFont.systemFont(ofSize: 25) if let richWorkspaceText = richWorkspaceText { @@ -133,12 +138,12 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat buttonTransfer.setImage(nil, for: .normal) buttonTransfer.layer.cornerRadius = 6 buttonTransfer.layer.masksToBounds = true - imageButtonTransfer.image = NCUtility().loadImage(named: "stop.circle") + imageButtonTransfer.image = UIImage(systemName: "stop.circle") imageButtonTransfer.tintColor = .white labelTransfer.text = "" progressTransfer.progress = 0 - progressTransfer.tintColor = NCBrandColor.shared.brandElement - progressTransfer.trackTintColor = NCBrandColor.shared.brandElement.withAlphaComponent(0.2) + progressTransfer.tintColor = NCBrandColor.shared.brand + progressTransfer.trackTintColor = NCBrandColor.shared.brand.withAlphaComponent(0.2) transferSeparatorBottom.backgroundColor = .separator transferSeparatorBottomHeightConstraint.constant = 0.5 } @@ -162,16 +167,19 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat buttonSwitch.isEnabled = enable buttonOrder.isEnabled = enable + buttonMore.isEnabled = enable } + func buttonMoreIsHidden(_ isHidden: Bool) { + buttonMore.isHidden = isHidden + } + func setImageSwitchList() { - - buttonSwitch.setImage(UIImage(systemName: "list.bullet"), for: .normal)//!.image(color: NCBrandColor.shared.iconColor, width: 20, height: 15), for: .normal) + buttonSwitch.setImage(UIImage(systemName: "list.bullet")!.image(color: NCBrandColor.shared.iconColor, width: 20, height: 15), for: .normal) } func setImageSwitchGrid() { - - buttonSwitch.setImage(UIImage(systemName: "square.grid.2x2")!.image(color: NCBrandColor.shared.iconImageColor, size: 20), for: .normal) + buttonSwitch.setImage(UIImage(systemName: "square.grid.2x2")!.image(color: NCBrandColor.shared.iconColor, size: 20), for: .normal) } func setButtonsView(height: CGFloat) { @@ -244,9 +252,9 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat var image: UIImage? if let ocId, let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) { - image = utility.getIcon(metadata: metadata)?.darken() + image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt256)?.darken() if image == nil { - image = utility.loadImage(named: metadata.iconName, useTypeIconFile: true) + image = UIImage(named: metadata.iconName) buttonTransfer.backgroundColor = .lightGray } else { buttonTransfer.backgroundColor = .clear @@ -258,11 +266,11 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat } } - if heightHeaderSection == 0 { - viewSection.isHidden = true - } else { - viewSection.isHidden = false - } +// if heightHeaderSection == 0 { +// viewSection.isHidden = true +// } else { +// viewSection.isHidden = false +// } self.collectionViewRecommendations.reloadData() } @@ -286,6 +294,10 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat @IBAction func touchUpInsideOrder(_ sender: Any) { delegate?.tapButtonOrder(sender) } + + @IBAction func touchUpInsideMore(_ sender: Any) { + delegate?.tapButtonMore(sender) + } @IBAction func touchUpTransfer(_ sender: Any) { delegate?.tapButtonTransfer(sender) diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.xib b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.xib index 528b2f0098..53ceade6de 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.xib +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.xib @@ -46,6 +46,9 @@ + + + @@ -177,6 +180,7 @@ + diff --git a/iOSClient/Menu/NCSortMenu.swift b/iOSClient/Menu/NCSortMenu.swift index 3aaadb4dcd..6b62e33b31 100644 --- a/iOSClient/Menu/NCSortMenu.swift +++ b/iOSClient/Menu/NCSortMenu.swift @@ -30,6 +30,7 @@ class NCSortMenu: NSObject { private var sortButton: UIButton? private var serverUrl: String = "" private var hideDirectoryOnTop: Bool? + private var account: String = "" private var key = "" @@ -39,6 +40,7 @@ class NCSortMenu: NSObject { self.sortButton = sortButton self.serverUrl = serverUrl self.hideDirectoryOnTop = hideDirectoryOnTop + self.account = account guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: account, key: key, serverUrl: serverUrl) else { return } var actions = [NCMenuAction]() @@ -47,10 +49,10 @@ class NCSortMenu: NSObject { if layoutForView.ascending { title = NSLocalizedString("_order_by_name_z_a_", comment: "") - icon = UIImage(named: "sortFileNameZA")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) + icon = UIImage(named: "sortFileNameZA")!.image(color: NCBrandColor.shared.iconColor, size: 50) } else { title = NSLocalizedString("_order_by_name_a_z_", comment: "") - icon = UIImage(named: "sortFileNameAZ")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) + icon = UIImage(named: "sortFileNameAZ")!.image(color: NCBrandColor.shared.iconColor, size: 50) } actions.append( @@ -69,10 +71,10 @@ class NCSortMenu: NSObject { if layoutForView.ascending { title = NSLocalizedString("_order_by_date_more_recent_", comment: "") - icon = UIImage(named: "sortDateMoreRecent")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) + icon = UIImage(named: "sortDateMoreRecent")!.image(color: NCBrandColor.shared.iconColor, size: 50) } else { title = NSLocalizedString("_order_by_date_less_recent_", comment: "") - icon = UIImage(named: "sortDateLessRecent")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) + icon = UIImage(named: "sortDateLessRecent")!.image(color: NCBrandColor.shared.iconColor, size: 50) } actions.append( @@ -91,10 +93,10 @@ class NCSortMenu: NSObject { if layoutForView.ascending { title = NSLocalizedString("_order_by_size_largest_", comment: "") - icon = UIImage(named: "sortLargest")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) + icon = UIImage(named: "sortLargest")!.image(color: NCBrandColor.shared.iconColor, size: 50) } else { title = NSLocalizedString("_order_by_size_smallest_", comment: "") - icon = UIImage(named: "sortSmallest")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) + icon = UIImage(named: "sortSmallest")!.image(color: NCBrandColor.shared.iconColor, size: 50) } actions.append( @@ -115,11 +117,12 @@ class NCSortMenu: NSObject { actions.append( NCMenuAction( title: NSLocalizedString("_directory_on_top_no_", comment: ""), - icon: UIImage(named: "foldersOnTop")!.image(color: NCBrandColor.shared.iconImageColor, size: 50), + icon: UIImage(named: "foldersOnTop")!.image(color: NCBrandColor.shared.iconColor, size: 50), selected: layoutForView.directoryOnTop, on: layoutForView.directoryOnTop, action: { _ in layoutForView.directoryOnTop = !layoutForView.directoryOnTop + NCKeychain().setDirectoryOnTop(account: self.account, value: layoutForView.directoryOnTop) self.actionMenu(layoutForView: layoutForView) } ) @@ -144,6 +147,11 @@ class NCSortMenu: NSObject { self.sortButton?.setTitle(NSLocalizedString(layoutForView.titleButtonHeader, comment: ""), for: .normal) NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView) + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeLayout, + object: nil, + userInfo: ["account": self.account, + "serverUrl": self.serverUrl, + "layoutForView": layoutForView]) NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) } } diff --git a/iOSClient/Offline/NCOffline.swift b/iOSClient/Offline/NCOffline.swift index 7154170163..637d396943 100644 --- a/iOSClient/Offline/NCOffline.swift +++ b/iOSClient/Offline/NCOffline.swift @@ -34,8 +34,8 @@ class NCOffline: NCCollectionViewCommon { layoutKey = NCGlobal.shared.layoutViewOffline enableSearchBar = false headerRichWorkspaceDisable = true - emptyImageName = "icloud.and.arrow.down" - emptyImage = UIImage(named: "folder") + emptyImageName = "folder_nmcloud" + emptyImage = UIImage(named: "folder_nmcloud") emptyTitle = "_files_no_files_" emptyDescription = "_tutorial_offline_view_" emptyDataPortaitOffset = 30 From c861411b1887bbf92bc7140fa3fd707e30e8efe4 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 6 Oct 2025 12:02:07 +0530 Subject: [PATCH 4/5] NMC 2172 - Dashboard theming customisation changes --- iOSClient/Files/NCFiles.swift | 181 +++++----- .../Cell/NCCellProtocol.swift | 1 + .../Collection Common/Cell/NCListCell.swift | 56 ++- .../Collection Common/Cell/NCListCell.xib | 39 +- ...ctionViewCommon+SelectTabBarDelegate.swift | 50 ++- ...mmon+SwipeCollectionViewCellDelegate.swift | 6 +- .../NCCollectionViewCommon.swift | 339 +++++++++++------- .../NCSectionFirstHeader.swift | 48 ++- iOSClient/Menu/NCSortMenu.swift | 2 +- 9 files changed, 466 insertions(+), 256 deletions(-) diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index d0d231fb17..5b8d99e0a2 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -31,6 +31,9 @@ class NCFiles: NCCollectionViewCommon { internal var fileNameBlink: String? internal var fileNameOpen: String? + internal var matadatasHash: String = "" + internal var semaphoreReloadDataSource = DispatchSemaphore(value: 1) + private var timerProcess: Timer? internal var lastOffsetY: CGFloat = 0 internal var lastScrollTime: TimeInterval = 0 @@ -57,10 +60,7 @@ class NCFiles: NCCollectionViewCommon { plusButton.setTitle("", for: .normal) plusButton.setImage(image, for: .normal) - plusButton.backgroundColor = NCBrandColor.shared.customer - if let activeTableAccount = NCManageDatabase.shared.getActiveTableAccount() { - self.plusButton.backgroundColor = NCBrandColor.shared.getElement(account: activeTableAccount.account) - } + plusButton.backgroundColor = NCBrandColor.shared.getElement(account: session.account) plusButton.accessibilityLabel = NSLocalizedString("_accessibility_add_upload_", comment: "") plusButton.layer.cornerRadius = plusButton.frame.size.width / 2.0 plusButton.layer.masksToBounds = false @@ -73,7 +73,7 @@ class NCFiles: NCCollectionViewCommon { self.plusButton.backgroundColor = NCBrandColor.shared.getElement(account: activeTableAccount.account) } } - + if self.serverUrl.isEmpty { /// @@ -87,6 +87,7 @@ class NCFiles: NCCollectionViewCommon { if let controller = userInfo["controller"] as? NCMainTabBarController, controller == self.controller { controller.account = account + controller.availableNotifications = false } else { return } @@ -98,6 +99,7 @@ class NCFiles: NCCollectionViewCommon { self.isEditMode = false self.fileSelect.removeAll() self.layoutForView = self.database.getLayoutForView(account: self.session.account, key: self.layoutKey, serverUrl: self.serverUrl) + self.gridLayout.column = CGFloat(self.layoutForView?.columnGrid ?? 3) if self.isLayoutList { self.collectionView?.collectionViewLayout = self.listLayout @@ -111,21 +113,21 @@ class NCFiles: NCCollectionViewCommon { ///Magentacloud branding changes hide user account button on left navigation bar // self.setNavigationLeftItems() - Task { - await self.reloadDataSource() - await self.getServerData() - } + self.dataSource.removeAll() + self.reloadDataSource() + self.getServerData() } } + self.timerProcess = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in + self.setNavigationRightItems(enableMenu: false) + }) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) resetPlusButtonAlpha() - Task { - await self.reloadDataSource() - } + reloadDataSource() } override func viewDidAppear(_ animated: Bool) { @@ -147,6 +149,8 @@ class NCFiles: NCCollectionViewCommon { await getServerData() } } + + self.showTipAutoUpload() } override func viewDidDisappear(_ animated: Bool) { @@ -160,55 +164,67 @@ class NCFiles: NCCollectionViewCommon { @IBAction func plusButtonAction(_ sender: UIButton) { resetPlusButtonAlpha() - guard let controller else { return } - let fileFolderPath = NCUtilityFileSystem().getFileNamePath("", serverUrl: serverUrl, session: NCSession.shared.getSession(controller: controller)) - let fileFolderName = (serverUrl as NSString).lastPathComponent - let capabilities = NKCapabilities.shared.getCapabilitiesBlocking(for: controller.account) - - if let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", controller.account, serverUrl)) { - if !directory.permissions.contains("CK") { - let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_add_file_") - NCContentPresenter().showWarning(error: error) + if let controller = UIApplication.shared.firstWindow?.rootViewController as? NCMainTabBarController { + let serverUrl = controller.currentServerUrl() + if let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", NCSession.shared.getSession(controller: controller).account, serverUrl)) { + if !directory.permissions.contains("CK") { + let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_add_file_") + NCContentPresenter().showWarning(error: error) + return + } + } + + let fileFolderPath = NCUtilityFileSystem().getFileNamePath("", serverUrl: serverUrl, session: NCSession.shared.getSession(controller: controller)) + let fileFolderName = (serverUrl as NSString).lastPathComponent + + if !FileNameValidator.checkFolderPath(fileFolderPath, account: controller.account) { + controller.present(UIAlertController.warning(message: "\(String(format: NSLocalizedString("_file_name_validator_error_reserved_name_", comment: ""), fileFolderName)) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) + return } - } - if !FileNameValidator.checkFolderPath(fileFolderPath, account: controller.account, capabilities: capabilities) { - controller.present(UIAlertController.warning(message: "\(String(format: NSLocalizedString("_file_name_validator_error_reserved_name_", comment: ""), fileFolderName)) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) - return + self.appDelegate.toggleMenu(controller: controller) } - - self.appDelegate.toggleMenu(controller: controller, sender: sender) + } - + // MARK: - DataSource - override func reloadDataSource() async { - guard !isSearchingMode else { - await super.reloadDataSource() - return + override func reloadDataSource() { + guard !isSearchingMode + else { + return super.reloadDataSource() } - let predicate: NSPredicate = { - if NCKeychain().getPersonalFilesOnly(account: self.session.account) { - return self.personalFilesOnlyPredicate - } else { - return self.defaultPredicate - } - }() + // Watchdog: this is only a fail safe "dead lock", I don't think the timeout will ever be called but at least nothing gets stuck, if after 5 sec. (which is a long time in this routine), the semaphore is still locked + // + if self.semaphoreReloadDataSource.wait(timeout: .now() + 5) == .timedOut { + self.semaphoreReloadDataSource.signal() + } - self.metadataFolder = await self.database.getMetadataFolderAsync(session: self.session, serverUrl: self.serverUrl) - if let tblDirectory = await self.database.getTableDirectoryAsync(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", self.session.account, self.serverUrl)) { - self.richWorkspaceText = tblDirectory.richWorkspace + var predicate = self.defaultPredicate + let predicateDirectory = NSPredicate(format: "account == %@ AND serverUrl == %@", session.account, self.serverUrl) + let dataSourceMetadatas = self.dataSource.getMetadatas() + + if NCKeychain().getPersonalFilesOnly(account: session.account) { + predicate = self.personalFilesOnlyPredicate } - let metadatas = await self.database.getMetadatasAsync(predicate: predicate, - withLayout: self.layoutForView, - withAccount: self.session.account) - self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, layoutForView: layoutForView, account: session.account) - await super.reloadDataSource() + self.metadataFolder = database.getMetadataFolder(session: session, serverUrl: self.serverUrl) + self.richWorkspaceText = database.getTableDirectory(predicate: predicateDirectory)?.richWorkspace + + let metadatas = self.database.getResultsMetadatasPredicate(predicate, layoutForView: layoutForView) + self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, layoutForView: layoutForView) - cachingAsync(metadatas: metadatas) + if metadatas.isEmpty { + self.semaphoreReloadDataSource.signal() + return super.reloadDataSource() + } + + self.dataSource.caching(metadatas: metadatas, dataSourceMetadatas: dataSourceMetadatas) { + self.semaphoreReloadDataSource.signal() + super.reloadDataSource() + } } override func getServerData(refresh: Bool = false) async { @@ -222,48 +238,54 @@ class NCFiles: NCCollectionViewCommon { Task { await networking.networkingTasks.cancel(identifier: "\(self.serverUrl)_NCFiles") } - guard !isSearchingMode else { return networkSearch() } - func downloadMetadata(_ metadata: tableMetadata) async -> Bool { + func downloadMetadata(_ metadata: tableMetadata) -> Bool { let fileSize = utilityFileSystem.fileProviderStorageSize(metadata.ocId, fileNameView: metadata.fileNameView) guard fileSize > 0 else { return false } - if let tblLocalFile = await database.getTableLocalFileAsync(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) { - if tblLocalFile.etag != metadata.etag { + if let localFile = database.getResultsTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))?.first { + if localFile.etag != metadata.etag { return true } } return false } - let resultsReadFolder = await networkReadFolderAsync(serverUrl: self.serverUrl, refresh: refresh) - guard resultsReadFolder.error == .success, resultsReadFolder.reloadRequired else { - return - } + DispatchQueue.global().async { + self.networkReadFolder { metadatas, isChanged, error in + DispatchQueue.main.async { + self.refreshControl.endRefreshing() - let metadatasForDownload: [tableMetadata] = resultsReadFolder.metadatas ?? self.dataSource.getMetadatas() - Task.detached(priority: .utility) { - for metadata in metadatasForDownload where !metadata.directory { - if await downloadMetadata(metadata) { - if let metadata = await self.database.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, - session: NCNetworking.shared.sessionDownload, - selector: NCGlobal.shared.selectorDownloadFile, - sceneIdentifier: self.controller?.sceneIdentifier) { - NCNetworking.shared.download(metadata: metadata) + if isChanged || self.isNumberOfItemsInAllSectionsNull { + self.reloadDataSource() + } + } + + if error == .success { + let metadatas: [tableMetadata] = metadatas ?? self.dataSource.getMetadatas() + for metadata in metadatas where !metadata.directory && downloadMetadata(metadata) { + self.database.setMetadatasSessionInWaitDownload(metadatas: [metadata], + session: NCNetworking.shared.sessionDownload, + selector: NCGlobal.shared.selectorDownloadFile, + sceneIdentifier: self.controller?.sceneIdentifier) + NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true) + } + /// Recommendation + if self.isRecommendationActived { + Task.detached { + await NCNetworking.shared.createRecommendations(session: self.session) + } } } } } - - await self.reloadDataSource() } - private func networkReadFolderAsync(serverUrl: String, refresh: Bool) async -> (metadatas: [tableMetadata]?, error: NKError, reloadRequired: Bool) { - let isDirectoryE2EE = await NCUtilityFileSystem().isDirectoryE2EEAsync(session: self.session, serverUrl: serverUrl) - let resultsReadFile = await NCNetworking.shared.readFileAsync(serverUrlFileName: serverUrl, account: session.account) { task in + private func networkReadFolder(completion: @escaping (_ metadatas: [tableMetadata]?, _ isDataChanged: Bool, _ error: NKError) -> Void) { + NCNetworking.shared.readFile(serverUrlFileName: serverUrl, account: session.account) { task in self.dataSourceTask = task if self.dataSource.isEmpty() { self.collectionView.reloadData() @@ -297,7 +319,6 @@ class NCFiles: NCCollectionViewCommon { if self.dataSource.isEmpty() { self.collectionView.reloadData() } - } guard error == .success else { return (nil, error, false) @@ -378,12 +399,11 @@ class NCFiles: NCCollectionViewCommon { errorCode: error.errorCode) } } - return (metadatas, error, true) } func blinkCell(fileName: String?) { if let fileName = fileName, let metadata = database.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", session.account, self.serverUrl, fileName)) { - let indexPath = self.dataSource.getIndexPathMetadata(ocId: metadata.ocId) + let indexPath = self.dataSource.getIndexPathMetadata(ocId: metadata.ocId).indexPath if let indexPath = indexPath { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { UIView.animate(withDuration: 0.3) { @@ -403,7 +423,7 @@ class NCFiles: NCCollectionViewCommon { func openFile(fileName: String?) { if let fileName = fileName, let metadata = database.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", session.account, self.serverUrl, fileName)) { - let indexPath = self.dataSource.getIndexPathMetadata(ocId: metadata.ocId) + let indexPath = self.dataSource.getIndexPathMetadata(ocId: metadata.ocId).indexPath if let indexPath = indexPath { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.collectionView(self.collectionView, didSelectItemAt: indexPath) @@ -411,7 +431,7 @@ class NCFiles: NCCollectionViewCommon { } } } - + override func resetPlusButtonAlpha(animated: Bool = true) { accumulatedScrollDown = 0 let update = { @@ -448,12 +468,9 @@ class NCFiles: NCCollectionViewCommon { let currentAccount = session.account if database.getAllTableAccount().isEmpty { - let navigationController: UINavigationController? - - if NCBrandOptions.shared.disable_intro, let viewController = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLogin") as? NCLogin { - navigationController = UINavigationController(rootViewController: viewController) - } else { - navigationController = UIStoryboard(name: "NCIntro", bundle: nil).instantiateInitialViewController() as? UINavigationController + if let navigationController = UIStoryboard(name: "NCIntro", bundle: nil).instantiateInitialViewController() as? UINavigationController { + navigationController.modalPresentationStyle = .fullScreen + self.present(navigationController, animated: true) } UIApplication.shared.mainAppWindow?.rootViewController = navigationController @@ -466,6 +483,6 @@ class NCFiles: NCCollectionViewCommon { navigationItem.title = self.titleCurrentFolder } - (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() +// (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() } } diff --git a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift index e1f97b5193..0d2b1b7e50 100644 --- a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift +++ b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift @@ -123,6 +123,7 @@ extension NCCellProtocol { get { return nil } set { } } + var fileProgressView: UIProgressView? { get { return nil } set {} diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 563c220b3d..19b9be2eb1 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -28,6 +28,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var imageSelect: UIImageView! @IBOutlet weak var imageStatus: UIImageView! @IBOutlet weak var imageFavorite: UIImageView! + @IBOutlet weak var imageFavoriteBackground: UIImageView! @IBOutlet weak var imageLocal: UIImageView! @IBOutlet weak var labelTitle: UILabel! @IBOutlet weak var labelInfo: UILabel! @@ -40,10 +41,15 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var progressView: UIProgressView! @IBOutlet weak var separator: UIView! @IBOutlet weak var labelShared: UILabel! + @IBOutlet weak var tag0: UILabel! + @IBOutlet weak var tag1: UILabel! + @IBOutlet weak var imageItemLeftConstraint: NSLayoutConstraint! @IBOutlet weak var separatorHeightConstraint: NSLayoutConstraint! @IBOutlet weak var subInfoTrailingConstraint: NSLayoutConstraint! - + @IBOutlet weak var iPadLabelTitleTrailingConstraint: NSLayoutConstraint! + @IBOutlet weak var iPhoneLabelTitleTrailingConstraint: NSLayoutConstraint! + private var ocId = "" private var ocIdTransfer = "" private var user = "" @@ -54,10 +60,15 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto var fileAvatarImageView: UIImageView? { return imageShared } + var fileOcId: String? { get { return ocId } set { ocId = newValue ?? "" } } + var fileOcIdTransfer: String? { + get { return ocIdTransfer } + set { ocIdTransfer = newValue ?? "" } + } var filePreviewImageView: UIImageView? { get { return imageItem } set { imageItem = newValue } @@ -120,13 +131,23 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto super.awakeFromNib() initCell() } - + func initCell() { imageItem.layer.cornerRadius = 6 imageItem.layer.masksToBounds = true + imageStatus.image = nil + imageFavorite.image = nil + imageFavoriteBackground.isHidden = true + imageLocal.image = nil + labelTitle.text = "" + labelInfo.text = "" + labelSubinfo.text = "" + imageShared.image = nil + imageMore.image = nil // use entire cell as accessibility element + accessibilityHint = nil accessibilityLabel = nil accessibilityValue = nil @@ -151,6 +172,9 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto progressView.transform = CGAffineTransform(scaleX: 1.0, y: 0.5) progressView.trackTintColor = .clear imageSelect.isHidden = true + + separatorHeightConstraint.constant = 0.5 + titleInfoTrailingDefault() let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(gestureRecognizer:))) longPressedGesture.minimumPressDuration = 0.5 @@ -162,14 +186,13 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto separatorHeightConstraint.constant = 0.5 titleInfoTrailingDefault() - labelTitle.text = "" - labelInfo.text = "" labelTitle.textColor = .label labelInfo.textColor = .systemGray labelSubinfo.textColor = .systemGray setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCImageCache.images.buttonMore) imageMore.isHidden = false buttonMore.isHidden = false + updateConstraintsForCurrentDevice() } override func prepareForReuse() { @@ -180,7 +203,24 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? { return nil } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + updateConstraintsForCurrentDevice() + } + func updateConstraintsForCurrentDevice() { + if labelShared?.isHidden == false { + iPhoneLabelTitleTrailingConstraint.isActive = false + iPadLabelTitleTrailingConstraint.isActive = true + } else { + iPhoneLabelTitleTrailingConstraint.isActive = true + iPadLabelTitleTrailingConstraint.isActive = false + } +// iPhoneLabelTitleTrailingConstraint.isActive = UIDevice.current.userInterfaceIdiom == .pad ? false : true +// iPadLabelTitleTrailingConstraint.isActive = UIDevice.current.userInterfaceIdiom == .pad ? true : false + } + @IBAction func touchUpInsideShare(_ sender: Any) { listCellDelegate?.tapShareListItem(with: ocId, ocIdTransfer: ocIdTransfer, sender: sender) } @@ -203,11 +243,11 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto UIAccessibilityCustomAction( name: NSLocalizedString("_share_", comment: ""), target: self, - selector: #selector(touchUpInsideShare(_:))), + selector: #selector(touchUpInsideShare)), UIAccessibilityCustomAction( name: NSLocalizedString(moreName, comment: ""), target: self, - selector: #selector(touchUpInsideMore(_:))) + selector: #selector(touchUpInsideMore)) ] } @@ -262,6 +302,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto func selected(_ status: Bool, isEditMode: Bool) { // NMC-1190 - iOS - Files - Deleting files while files are still uploading won't delete properly : to fix this issue remove check for !metadata.isInTransfer in below line guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId), !metadata.isInTransfer, !metadata.e2eEncrypted else { +// guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId), !metadata.e2eEncrypted else { backgroundView = nil separator.isHidden = false imageSelect.isHidden = true @@ -283,6 +324,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } blurEffectView?.frame = self.bounds blurEffectView?.autoresizingMask = [.flexibleWidth, .flexibleHeight] + imageSelect.image = NCImageCache.images.checkedYes backgroundView = blurEffectView imageSelect.image = NCImageCache.images.checkedYes separator.isHidden = true @@ -294,7 +336,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } func writeInfoDateSize(date: NSDate, size: Int64) { - labelInfo.text = NCUtility().dateDiff(date as Date) + " · " + NCUtilityFileSystem().transformedSize(size) + labelInfo.text = NCUtility().getRelativeDateTitle(date as Date) + " · " + NCUtilityFileSystem().transformedSize(size) labelSubinfo.text = "" } diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.xib b/iOSClient/Main/Collection Common/Cell/NCListCell.xib index a9b542e428..e7b2e84c4e 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.xib @@ -1,10 +1,10 @@ - + - - + + @@ -31,6 +31,14 @@ + @@ -53,7 +61,7 @@ + + + + + + diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift index 2b97cde007..e543307860 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift @@ -58,8 +58,12 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate, NC Task { var error = NKError() + var ocId: [String] = [] for metadata in copyMetadatas where error == .success { - error = await self.networking.deleteCache(metadata, sceneIdentifier: self.controller?.sceneIdentifier) + error = await NCNetworking.shared.deleteCache(metadata, sceneIdentifier: self.controller?.sceneIdentifier) + if error == .success { + ocId.append(metadata.ocId) + } } await self.setEditMode(false) } @@ -163,7 +167,10 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate, NC func convertLivePhoto(metadataFirst: tableMetadata?, metadataLast: tableMetadata?) { if let metadataFirst, let metadataLast { Task { - await self.networking.setLivePhoto(metadataFirst: metadataFirst, metadataLast: metadataLast) + let userInfo: [String: Any] = ["serverUrl": metadataFirst.serverUrl, + "account": metadataFirst.account] + + await NCNetworking.shared.setLivePhoto(metadataFirst: metadataFirst, metadataLast: metadataLast, userInfo: userInfo) } } setEditMode(false) @@ -240,14 +247,43 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate, NC } if canOpenIn { - actions.append(.openInAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: { self.toggleSelect() })) + actions.append(.share(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: { self.toggleSelect() })) } - if !isAnyFolder, canUnlock, !NCCapabilities.shared.getCapabilities(account: controller?.account).capabilityFilesLockVersion.isEmpty { + if !isAnyFolder, canUnlock, !NCCapabilities.shared.getCapabilities(account: controller?.account).capabilityFilesLockVersion.isEmpty { actions.append(.lockUnlockFiles(shouldLock: !isAnyLocked, metadatas: selectedMetadatas, completion: { self.toggleSelect() })) } if !selectedMediaMetadatas.isEmpty { +// var title: String = NSLocalizedString("_save_selected_files_", comment: "") +// var icon = NCUtility().loadImage(named: "save_files",colors: [NCBrandColor.shared.iconImageColor]) +// if selectedMediaMetadatas.allSatisfy({ NCManageDatabase.shared.getMetadataLivePhoto(metadata: $0) != nil }) { +// title = NSLocalizedString("_livephoto_save_", comment: "") +// icon = NCUtility().loadImage(named: "livephoto") +// } +// +// actions.append(NCMenuAction( +// title: title, +// icon: icon, +// order: 0, +// action: { _ in +// for metadata in selectedMediaMetadatas { +// if let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) { +// NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV, hudView: self.view)) +// } else { +// if NCUtilityFileSystem().fileProviderStorageExists(metadata) { +// NCActionCenter.shared.saveAlbum(metadata: metadata, controller: self.tabBarController as? NCMainTabBarController) +// } else { +// if NCNetworking.shared.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty { +// NCNetworking.shared.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum)) +// } +// } +// } +// } +// self.toggleSelect() +// } +// ) +// ) actions.append(.saveMediaAction(selectedMediaMetadatas: selectedMediaMetadatas, controller: self.controller, completion: { self.toggleSelect() })) } actions.append(.setAvailableOfflineAction(selectedMetadatas: selectedMetadatas, isAnyOffline: isAnyOffline, viewController: self, completion: { @@ -256,10 +292,10 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate, NC })) if !isDirectoryE2EE { - actions.append(.moveOrCopyAction(selectedMetadatas: selectedMetadatas, viewController: self, indexPath: [], completion: { self.toggleSelect() })) - actions.append(.copyAction(selectOcId: selectOcId, viewController: self, completion: { self.toggleSelect() })) + actions.append(.moveOrCopyAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: { self.toggleSelect() })) + actions.append(.copyAction(fileSelect: fileSelect, controller: self.controller, completion: { self.toggleSelect() })) } - actions.append(.deleteAction(selectedMetadatas: selectedMetadatas, indexPaths: [], viewController: self, completion: { self.toggleSelect() })) + actions.append(.deleteAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: { self.toggleSelect() })) return actions } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift index 11a580f6a3..8a08faca4e 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift @@ -46,7 +46,7 @@ extension NCCollectionViewCommon: SwipeCollectionViewCellDelegate { var actions = [favoriteAction] let shareAction = SwipeAction(style: .default, title: NSLocalizedString("_share_", comment: "")) { _, _ in - NCActionCenter.shared.openActivityViewController(selectedMetadata: [metadata]) + NCActionCenter.shared.openActivityViewController(selectedMetadata: [metadata], controller: self.controller) } shareAction.backgroundColor = .blue shareAction.image = .init(systemName: "square.and.arrow.up") @@ -64,11 +64,11 @@ extension NCCollectionViewCommon: SwipeCollectionViewCellDelegate { let message = NSLocalizedString("_want_delete_", comment: "") + "\n - " + metadata.fileNameView - let alertController = UIAlertController.deleteFileOrFolder(titleString: titleDelete + "?", message: message, canDeleteServer: !metadata.lock, selectedMetadatas: [metadata], indexPaths: self.selectIndexPaths) { _ in } + let alertController = UIAlertController.deleteFileOrFolder(titleString: titleDelete + "?", message: message, canDeleteServer: !metadata.lock, selectedMetadatas: [metadata], sceneIdentifier: self.controller?.sceneIdentifier) { _ in } self.viewController.present(alertController, animated: true, completion: nil) } - deleteAction.image = .init(systemName: "trash") + deleteAction.image = UIImage.init(systemName: "trash") deleteAction.style = .destructive deleteAction.transitionDelegate = scaleTransition deleteAction.hidesWhenSelected = true diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index c7b6af690a..cdb67aa1a4 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -23,13 +23,14 @@ import UIKit import SwiftUI +import Realm import RealmSwift import NextcloudKit import EasyTipView import LucidBanner +import MoEngageInApps -//class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionFirstHeaderDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { -class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionHeaderMenuDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate, NCEmptyDataSetDelegate { +class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionHeaderMenuDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate, NCEmptyDataSetDelegate { @IBOutlet weak var collectionView: UICollectionView! @@ -39,7 +40,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let utilityFileSystem = NCUtilityFileSystem() let imageCache = NCImageCache.shared var dataSource = NCCollectionViewDataSource() - let networking = NCNetworking.shared let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! var pinchGesture: UIPinchGestureRecognizer = UIPinchGestureRecognizer() @@ -71,6 +71,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var attributesZoomIn: UIMenuElement.Attributes = [] var attributesZoomOut: UIMenuElement.Attributes = [] let maxImageGrid: CGFloat = 7 +// var headerMenu: NCSectionFirstHeader? + var tipViewAccounts: EasyTipView? var syncMetadatasTask: Task? @@ -85,6 +87,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var titleCurrentFolder = "" var titlePreviusFolder: String? var enableSearchBar: Bool = false + var groupByField = "name" var emptyImageName: String? @@ -108,14 +111,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var numberOfColumns: Int = 0 var lastNumberOfColumns: Int = 0 - let heightHeaderRecommendations: CGFloat = 160 - let heightHeaderSection: CGFloat = 30 - var isTransitioning: Bool = false var selectableDataSource: [RealmSwiftObject] { dataSource.getMetadataSourceForAllSections() } var pushed: Bool = false var emptyDataSet: NCEmptyDataSet? + let heightHeaderRecommendations: CGFloat = 160 + let heightHeaderSection: CGFloat = 30 + var session: NCSession.Session { NCSession.shared.getSession(controller: tabBarController) } @@ -138,7 +141,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var isRecommendationActived: Bool { self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session) && - capabilities.recommendations + NCCapabilities.shared.getCapabilities(account: self.session.account).capabilityRecommendations } var infoLabelsSeparator: String { @@ -149,17 +152,13 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.tabBarController as? NCMainTabBarController } - var sceneIdentifier: String { - (self.tabBarController as? NCMainTabBarController)?.sceneIdentifier ?? "" - } - var defaultPredicate: NSPredicate { - let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, self.global.metadataStatusHideInView, NKTypeClassFile.video.rawValue) + let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, NCGlobal.shared.metadataStatusHideInView, NKCommon.TypeClassFile.video.rawValue) return predicate } var personalFilesOnlyPredicate: NSPredicate { - let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND (ownerId == %@ || ownerId == '') AND mountType == '' AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, session.userId, global.metadataStatusHideInView, NKTypeClassFile.video.rawValue) + let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND (ownerId == %@ || ownerId == '') AND mountType == '' AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, session.userId, global.metadataStatusHideInView, NKCommon.TypeClassFile.video.rawValue) return predicate } @@ -234,15 +233,19 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS collectionView.refreshControl = refreshControl refreshControl.action(for: .valueChanged) { _ in - Task { - await self.getServerData(refresh: true) + self.dataSource.removeAll() + self.getServerData() + if self.isRecommendationActived { + Task.detached { + await NCNetworking.shared.createRecommendations(session: self.session) + } } self.refreshControl.endRefreshing() DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { self.resetPlusButtonAlpha() } } - + // Empty emptyDataSet = NCEmptyDataSet(view: collectionView, offset: getHeaderHeight(), delegate: self) @@ -262,11 +265,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let dropInteraction = UIDropInteraction(delegate: self) self.navigationController?.navigationItem.leftBarButtonItems?.first?.customView?.addInteraction(dropInteraction) - - NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterChangeTheming), object: nil, queue: .main) { [weak self] _ in - guard let self else { return } - self.collectionView.reloadData() - } + if(!UserDefaults.standard.bool(forKey: "isInitialPrivacySettingsShowed") || isApplicationUpdated()){ redirectToPrivacyViewController() @@ -275,7 +274,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS UserDefaults.standard.set(appVersion, forKey: "CurrentAppVersion") } -// NotificationCenter.default.addObserver(self, selector: #selector(changeTheming(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeTheming), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(changeTheming(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeTheming), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(reloadDataSource(_:)), name: NSNotification.Name(rawValue: global.notificationCenterReloadDataSource), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(getServerData(_:)), name: NSNotification.Name(rawValue: global.notificationCenterGetServerData), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(reloadHeader(_:)), name: NSNotification.Name(rawValue: global.notificationCenterReloadHeader), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(updateIcons), name: NSNotification.Name(rawValue: global.notificationCenterUpdateIcons), object: nil) DispatchQueue.main.async { self.collectionView?.collectionViewLayout.invalidateLayout() @@ -285,6 +288,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + AnalyticsHelper.shared.displayInAppNotification() + if titlePreviusFolder != nil { navigationController?.navigationBar.topItem?.title = titlePreviusFolder } @@ -294,6 +299,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS navigationController?.setNavigationBarHidden(false, animated: true) appDelegate.activeViewController = self + appDelegate.account = session.account + appDelegate.urlBase = session.urlBase + appDelegate.userId = session.userId + appDelegate.user = session.user + + NCKeychain().setAccountName(account: session.account) isEditMode = false /// Magentacloud branding changes hide user account button on left navigation bar @@ -322,7 +333,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - self.networking.addDelegate(self) + NCNetworking.shared.transferDelegate = self NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(closeRichWorkspaceWebView), name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) @@ -355,25 +366,51 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - self.networking.cancelUnifiedSearchFiles() + NCNetworking.shared.cancelUnifiedSearchFiles() dismissTip() pushed = false toggleSelect(isOn: false) // Cancel Queue & Retrieves Properties - self.networking.downloadThumbnailQueue.cancelAll() - self.networking.unifiedSearchQueue.cancelAll() + NCNetworking.shared.downloadThumbnailQueue.cancelAll() + NCNetworking.shared.unifiedSearchQueue.cancelAll() dataSourceTask?.cancel() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - self.networking.removeDelegate(self) + NCNetworking.shared.transferDelegate = nil NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) - - removeImageCache(metadatas: self.dataSource.getMetadatas()) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterChangeStatusFolderE2EE), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterChangeLayout), object: nil) + + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDeleteFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCopyMoveFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCopyFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterMoveFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterRenameFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCreateFolder), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterFavoriteFile), object: nil) + + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterMoveFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCreateFolder), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterFavoriteFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadStartFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadedFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadCancelFile), object: nil) + + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadStartFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadedFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadedLivePhoto), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadCancelFile), object: nil) + + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUpdateShare), object: nil) + + dataSource.removeImageCache() } func isApplicationUpdated() -> Bool { @@ -528,12 +565,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS @objc func applicationWillResignActive(_ notification: NSNotification) { self.resetPlusButtonAlpha() - refreshControlEndRefreshing() + self.refreshControl.endRefreshing() } @objc func reloadAvatar(_ notification: NSNotification) { DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - self.showTip() + self.showTipAccounts() } guard let userInfo = notification.userInfo as NSDictionary?, let error = userInfo["error"] as? NKError, @@ -546,20 +583,18 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.reloadDataSource() } - @objc func closeRichWorkspaceWebView() { - Task { - await self.reloadDataSource() - } - } - - // MARK: - Layout + @objc func changeLayout(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + let serverUrl = userInfo["serverUrl"] as? String, + let layoutForView = userInfo["layoutForView"] as? NCDBLayoutForView, + account == session.account, + serverUrl == self.serverUrl + else { return } - func changeLayout(layoutForView: NCDBLayoutForView) { if self.layoutForView?.layout == layoutForView.layout { self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) - Task { - await self.reloadDataSource() - } + self.reloadDataSource() return } @@ -582,7 +617,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.collectionView.collectionViewLayout.invalidateLayout() -// (self.navigationController as? NCMainNavigationController)?.updateRightMenu() +// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() } @objc func reloadDataSource(_ notification: NSNotification) { @@ -860,7 +895,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let transfer = NCTransferProgress.shared.append(NCTransferProgress.Transfer(ocId: ocId, ocIdTransfer: ocIdTransfer, session: session, chunk: chunk, e2eEncrypted: e2eEncrypted, progressNumber: progressNumber, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected)) - DispatchQueue.main.async { +// DispatchQueue.main.async { if self.headerMenuTransferView && (chunk > 0 || e2eEncrypted) { if NCNetworking.shared.transferInForegorund?.ocId == ocId { NCNetworking.shared.transferInForegorund?.progress = progressNumber.floatValue @@ -870,7 +905,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } self.headerMenu?.progressTransfer.progress = transfer.progressNumber.floatValue } else { - guard let indexPath = self.dataSource.getIndexPathMetadata(ocId: ocId).indexPath, + guard let indexPath = self.dataSource.getIndexPathMetadata(ocId: ocId), let cell = self.collectionView?.cellForItem(at: indexPath), let cell = cell as? NCCellProtocol else { return } if progressNumber.floatValue == 1 && !(cell is NCTransferCell) { @@ -902,24 +937,29 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } } - } +// } } @objc func updateShare(_ notification: NSNotification) { if isSearchingMode { networkSearch() } else { - self.dataSource.removeAll() +// self.dataSource.removeAll() getServerData() } } + @objc func updateIcons() { + collectionView.reloadData() +// reloadDataSource() + } + // MARK: - Layout func setNavigationLeftItems() { navigationItem.title = titleCurrentFolder } - + func getNavigationTitle() -> String { let tableAccount = self.database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) if let tableAccount, @@ -1019,13 +1059,9 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS isSearchingMode = true self.providers?.removeAll() self.dataSource.removeAll() - Task { - await self.reloadDataSource() - } + self.reloadDataSource() // TIP dismissTip() - // - isHiddenPlusButton(true) } func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { @@ -1035,17 +1071,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { - self.networking.cancelUnifiedSearchFiles() - + NCNetworking.shared.cancelUnifiedSearchFiles() self.isSearchingMode = false self.literalSearch = "" self.providers?.removeAll() self.dataSource.removeAll() - Task { - await self.reloadDataSource() - } - // - isHiddenPlusButton(false) + self.reloadDataSource() } // MARK: - TAP EVENT @@ -1053,6 +1084,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS func tapMoreListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: image, sender: sender) } + + func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + } func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: image, sender: sender) @@ -1071,6 +1106,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS func tapMoreGridItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { if isEditMode { return } guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } +// toggleMenu(metadata: metadata, image: image) if namedButtonMore == NCGlobal.shared.buttonMoreMore || namedButtonMore == NCGlobal.shared.buttonMoreLock { toggleMenu(metadata: metadata, image: image) } else if namedButtonMore == NCGlobal.shared.buttonMoreStop { @@ -1079,6 +1115,16 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } } + + func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } +// toggleMenu(metadata: metadata, image: image) + Task { + await cancelSession(metadata: metadata) + } + } + + func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } func tapRichWorkspace(_ sender: Any) { if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { @@ -1093,8 +1139,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } - func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { - toggleMenu(metadata: metadata, image: image, sender: sender) + func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?) { + toggleMenu(metadata: metadata, image: image) } func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { @@ -1133,16 +1179,18 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func longPressListItem(with objectId: String, indexPath: IndexPath, gestureRecognizer: UILongPressGestureRecognizer) { + } func longPressListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } func longPressMoreListItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - + func longPressGridItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } func longPressMoreGridItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } - + @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) } @@ -1164,7 +1212,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS becomeFirstResponder() if !serverUrl.isEmpty { - listMenuItems.append(UIMenuItem(title: NSLocalizedString("_paste_file_", comment: ""), action: #selector(pasteFilesMenu(_:)))) + listMenuItems.append(UIMenuItem(title: NSLocalizedString("_paste_file_", comment: ""), action: #selector(pasteFilesMenu))) } if !listMenuItems.isEmpty { @@ -1173,16 +1221,23 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } + // MARK: - Transfer Delegate + + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + + func tranferChange(status: String, metadata: tableMetadata, error: NKError) { } + // MARK: - Menu Item override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { - if #selector(pasteFilesMenu(_:)) == action { + + if #selector(pasteFilesMenu) == action { if !UIPasteboard.general.items.isEmpty, !(metadataFolder?.e2eEncrypted ?? false) { return true } - } else if #selector(copyMenuFile(_:)) == action { + } else if #selector(copyMenuFile) == action { return true - } else if #selector(moveMenuFile(_:)) == action { + } else if #selector(moveMenuFile) == action { return true } @@ -1266,7 +1321,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - DataSource - @objc func reloadDataSource() async { + @objc func reloadDataSource() { // get auto upload folder autoUploadFileName = NCManageDatabase.shared.getAccountAutoUploadFileName() @@ -1274,7 +1329,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // get layout for view layoutForView = NCManageDatabase.shared.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) - // set GroupField for Grid if !isSearchingMode && layoutForView?.layout == NCGlobal.shared.layoutGrid { groupByField = "classFile" @@ -1285,29 +1339,23 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS isDirectoryEncrypted = false } else { isDirectoryEncrypted = NCUtilityFileSystem().isDirectoryE2EE(session: session, serverUrl: serverUrl) - if isRecommendationActived { - Task.detached { - await self.networking.createRecommendations(session: self.session, serverUrl: self.serverUrl, collectionView: self.collectionView) - } - } } DispatchQueue.main.async { - UIView.transition(with: self.collectionView, - duration: 0.20, - options: .transitionCrossDissolve, - animations: { self.collectionView.reloadData() }, - completion: nil) - - (self.navigationController as? NCMainNavigationController)?.updateRightMenu() +// UIView.transition(with: self.collectionView, +// duration: 0.20, +// options: .transitionCrossDissolve, +// animations: { self.collectionView.reloadData() }, +// completion: nil) +// +// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() self.refreshControl.endRefreshing() self.collectionView.reloadData() self.setNavigationRightItems() } } - func getServerData(refresh: Bool = false) async { - dataSourceTask?.cancel() + func getServerData() { } @objc func networkSearch() { @@ -1317,59 +1365,45 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS guard !session.account.isEmpty, let literalSearch = literalSearch, !literalSearch.isEmpty else { - return + return self.refreshControl.endRefreshing() } self.networkSearchInProgress = true self.dataSource.removeAll() - Task { - await self.reloadDataSource() - } + self.refreshControl.beginRefreshing() + self.reloadDataSource() - if capabilities.serverVersionMajor >= global.nextcloudVersion20 { - self.networking.unifiedSearchFiles(literal: literalSearch, account: session.account) { task in + if NCCapabilities.shared.getCapabilities(account: session.account).capabilityServerVersionMajor >= global.nextcloudVersion20 { + NCNetworking.shared.unifiedSearchFiles(literal: literalSearch, account: session.account) { task in self.dataSourceTask = task - Task { - await self.reloadDataSource() - } - } providers: { account, searchProviders in + self.reloadDataSource() + } providers: { _, searchProviders in self.providers = searchProviders self.searchResults = [] - self.dataSource = NCCollectionViewDataSource(metadatas: [], layoutForView: self.layoutForView, providers: self.providers, searchResults: self.searchResults, account: account) + self.dataSource = NCCollectionViewDataSource(metadatas: [], layoutForView: self.layoutForView, providers: self.providers, searchResults: self.searchResults) } update: { _, _, searchResult, metadatas in guard let metadatas, !metadatas.isEmpty, self.isSearchingMode, let searchResult else { return } - self.networking.unifiedSearchQueue.addOperation(NCCollectionViewUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult)) + NCNetworking.shared.unifiedSearchQueue.addOperation(NCCollectionViewUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult)) } completion: { _, _ in - Task { - await self.reloadDataSource() - } + self.refreshControl.endRefreshing() + self.reloadDataSource() self.networkSearchInProgress = false } } else { - self.networking.searchFiles(literal: literalSearch, account: session.account) { task in + NCNetworking.shared.searchFiles(literal: literalSearch, account: session.account) { task in self.dataSourceTask = task - Task { - await self.reloadDataSource() - } + self.reloadDataSource() } completion: { metadatasSearch, error in - Task { - guard let metadatasSearch, - error == .success, - self.isSearchingMode - else { - self.networkSearchInProgress = false - await self.reloadDataSource() - return - } - let ocId = metadatasSearch.map { $0.ocId } - let metadatas = await self.database.getMetadatasAsync(predicate: NSPredicate(format: "ocId IN %@", ocId), - withLayout: self.layoutForView, - withAccount: self.session.account) - - self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, layoutForView: self.layoutForView, providers: self.providers, searchResults: self.searchResults, account: self.session.account) - self.networkSearchInProgress = false - await self.reloadDataSource() + DispatchQueue.main.async { + self.refreshControl.endRefreshing() + self.reloadDataSource() } + guard let metadatasSearch, error == .success, self.isSearchingMode else { return } + let ocId = metadatasSearch.map { $0.ocId } + let metadatas = self.database.getResultsMetadatasPredicate(NSPredicate(format: "ocId IN %@", ocId), layoutForView: self.layoutForView) + + self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, layoutForView: self.layoutForView, providers: self.providers, searchResults: self.searchResults) + self.networkSearchInProgress = false } } } @@ -1380,11 +1414,9 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS metadataForSection.unifiedSearchInProgress = true self.collectionView?.reloadData() - self.networking.unifiedSearchFilesProvider(id: lastSearchResult.id, term: term, limit: 5, cursor: cursor, account: session.account) { task in + NCNetworking.shared.unifiedSearchFilesProvider(id: lastSearchResult.id, term: term, limit: 5, cursor: cursor, account: session.account) { task in self.dataSourceTask = task - Task { - await self.reloadDataSource() - } + self.reloadDataSource() } completion: { _, searchResult, metadatas, error in if error != .success { Task {@MainActor in @@ -1436,6 +1468,65 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - Header size +// func getHeaderHeight(section: Int) -> (heightHeaderCommands: CGFloat, heightHeaderRichWorkspace: CGFloat, heightHeaderSection: CGFloat) { +// var headerRichWorkspace: CGFloat = 0 +// +// func getHeaderHeight() -> CGFloat { +// var size: CGFloat = 0 +// +// if isHeaderMenuTransferViewEnabled() != nil { +// if !isSearchingMode { +// size += global.heightHeaderTransfer +// } +// } +// if headerMenuButtonsView { +// size += NCGlobal.shared.heightButtonsView +// } +// return size +// } +// +//// func getHeaderHeight(section: Int) -> (heightHeaderRichWorkspace: CGFloat, +//// heightHeaderRecommendations: CGFloat, +//// heightHeaderSection: CGFloat) { +//// var heightHeaderRichWorkspace: CGFloat = 0 +//// var heightHeaderRecommendations: CGFloat = 0 +//// var heightHeaderSection: CGFloat = 0 +//// +//// if showDescription, +//// !isSearchingMode, +//// let richWorkspaceText = self.richWorkspaceText, +//// !richWorkspaceText.trimmingCharacters(in: .whitespaces).isEmpty { +//// heightHeaderRichWorkspace = UIScreen.main.bounds.size.height / 6 +//// } +//// +//// if isRecommendationActived, +//// !isSearchingMode, +//// NCKeychain().showRecommendedFiles, +//// !self.database.getRecommendedFiles(account: self.session.account).isEmpty { +//// heightHeaderRecommendations = self.heightHeaderRecommendations +//// heightHeaderSection = self.heightHeaderSection +//// } +// +// if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { +// if section == 0 { +// return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) +// } else { +// return (0, 0, self.heightHeaderSection) +// } +// } else { +// return (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) +//// +//// if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { +//// if section == 0 { +//// return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) +//// } else { +//// return (0, 0, self.heightHeaderSection) +//// } +//// } else { +//// return (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) +// } +// } + func isHeaderMenuTransferViewEnabled() -> [tableMetadata]? { if headerMenuTransferView, NCNetworking.shared.isOnline, diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index 558b0bad53..83745fc15d 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -32,7 +32,7 @@ protocol NCSectionFirstHeaderDelegate: AnyObject { func tapButtonTransfer(_ sender: Any) func tapRichWorkspace(_ sender: Any) func tapRecommendations(with metadata: tableMetadata) - func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) + func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?) } extension NCSectionFirstHeaderDelegate { @@ -74,12 +74,10 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat private weak var delegate: NCSectionFirstHeaderDelegate? private let utility = NCUtility() private var markdownParser = MarkdownParser() - private let global = NCGlobal.shared private var richWorkspaceText: String? private let richWorkspaceGradient: CAGradientLayer = CAGradientLayer() private var recommendations: [tableRecommendedFiles] = [] private var viewController: UIViewController? - private var sceneIdentifier: String = "" override func awakeFromNib() { super.awakeFromNib() @@ -206,7 +204,6 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat heightHeaderSection: CGFloat, sectionText: String?, viewController: UIViewController?, - sceneItentifier: String, delegate: NCSectionFirstHeaderDelegate?) { viewRichWorkspaceHeightConstraint.constant = heightHeaderRichWorkspace viewRecommendationsHeightConstraint.constant = heightHeaderRecommendations @@ -220,7 +217,6 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat self.recommendations = recommendations self.labelSection.text = sectionText self.viewController = viewController - self.sceneIdentifier = sceneItentifier self.delegate = delegate if heightHeaderRichWorkspace != 0, let richWorkspaceText, !richWorkspaceText.isEmpty { @@ -252,6 +248,9 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat var image: UIImage? if let ocId, let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) { +// image = utility.getIcon(metadata: metadata)?.darken() +// if image == nil { +// image = utility.loadImage(named: metadata.iconName, useTypeIconFile: true) image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt256)?.darken() if image == nil { image = UIImage(named: metadata.iconName) @@ -318,7 +317,7 @@ extension NCSectionFirstHeader: UICollectionViewDataSource { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? NCRecommendationsCell else { fatalError() } if let metadata = NCManageDatabase.shared.getMetadataFromFileId(recommendedFiles.id) { - let imagePreview = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: global.previewExt512) + let imagePreview = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt512) if metadata.directory { cell.image.image = self.utility.loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) @@ -330,23 +329,20 @@ extension NCSectionFirstHeader: UICollectionViewDataSource { cell.image.image = self.utility.loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) cell.image.contentMode = .scaleAspectFit if recommendedFiles.hasPreview { - Task { - let resultsPreview = await NextcloudKit.shared.downloadPreviewAsync(fileId: metadata.fileId, etag: metadata.etag, account: metadata.account) - if resultsPreview.error == .success, let data = resultsPreview.responseData?.data { + NextcloudKit.shared.downloadPreview(fileId: metadata.fileId, account: metadata.account) { _, _, _, _, responseData, error in + if error == .success, let data = responseData?.data { self.utility.createImageFileFrom(data: data, ocId: metadata.ocId, etag: metadata.etag) - if let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: self.global.previewExt512) { - Task { @MainActor in - for case let cell as NCRecommendationsCell in self.collectionViewRecommendations.visibleCells { - if cell.id == recommendedFiles.id { - cell.image.contentMode = .scaleAspectFill - if metadata.classFile == NKTypeClassFile.document.rawValue { - cell.setImageCorner(withBorder: true) - } - UIView.transition(with: cell.image, duration: 0.75, options: .transitionCrossDissolve, animations: { - cell.image.image = image - }, completion: nil) - break + if let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt512) { + for case let cell as NCRecommendationsCell in self.collectionViewRecommendations.visibleCells { + if cell.id == recommendedFiles.id { + cell.image.contentMode = .scaleAspectFill + if metadata.classFile == NKCommon.TypeClassFile.document.rawValue { + cell.setImageCorner(withBorder: true) } + UIView.transition(with: cell.image, duration: 0.75, options: .transitionCrossDissolve, animations: { + cell.image.image = image + }, completion: nil) + break } } } @@ -355,7 +351,7 @@ extension NCSectionFirstHeader: UICollectionViewDataSource { } } - if metadata.hasPreview, metadata.classFile == NKTypeClassFile.document.rawValue, imagePreview != nil { + if metadata.hasPreview, metadata.classFile == NKCommon.TypeClassFile.document.rawValue, imagePreview != nil { cell.setImageCorner(withBorder: true) } else { cell.setImageCorner(withBorder: false) @@ -387,7 +383,7 @@ extension NCSectionFirstHeader: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { let recommendedFiles = self.recommendations[indexPath.row] guard let metadata = NCManageDatabase.shared.getMetadataFromFileId(recommendedFiles.id), - metadata.classFile != NKTypeClassFile.url.rawValue, + metadata.classFile != NKCommon.TypeClassFile.url.rawValue, let viewController else { return nil } @@ -398,7 +394,7 @@ extension NCSectionFirstHeader: UICollectionViewDelegate { return nil #else return UIContextMenuConfiguration(identifier: identifier, previewProvider: { - return NCViewerProviderContextMenu(metadata: metadata, image: image, sceneIdentifier: self.sceneIdentifier) + return NCViewerProviderContextMenu(metadata: metadata, image: image) }, actionProvider: { _ in let cell = collectionView.cellForItem(at: indexPath) let contextMenu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: viewController, sceneIdentifier: self.sceneIdentifier, image: image, sender: cell) @@ -417,7 +413,7 @@ extension NCSectionFirstHeader: UICollectionViewDelegateFlowLayout { } extension NCSectionFirstHeader: NCRecommendationsCellDelegate { - func touchUpInsideButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { - self.delegate?.tapRecommendationsButtonMenu(with: metadata, image: image, sender: sender) + func touchUpInsideButtonMenu(with metadata: tableMetadata, image: UIImage?) { + self.delegate?.tapRecommendationsButtonMenu(with: metadata, image: image) } } diff --git a/iOSClient/Menu/NCSortMenu.swift b/iOSClient/Menu/NCSortMenu.swift index 6b62e33b31..f72dcb7197 100644 --- a/iOSClient/Menu/NCSortMenu.swift +++ b/iOSClient/Menu/NCSortMenu.swift @@ -122,7 +122,7 @@ class NCSortMenu: NSObject { on: layoutForView.directoryOnTop, action: { _ in layoutForView.directoryOnTop = !layoutForView.directoryOnTop - NCKeychain().setDirectoryOnTop(account: self.account, value: layoutForView.directoryOnTop) +// NCKeychain().setDirectoryOnTop(account: self.account, value: layoutForView.directoryOnTop) self.actionMenu(layoutForView: layoutForView) } ) From 29b74a8359347417c13e9627abcbba3f7fb14959 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 16 Dec 2025 17:32:06 +0530 Subject: [PATCH 5/5] NMC 2172 - Dashboard theming customisation changes --- iOSClient/Favorites/NCFavorite.swift | 1 - iOSClient/Files/NCFiles.swift | 409 ++++--- .../Cell/NCCellProtocol.swift | 23 +- .../Collection Common/Cell/NCListCell.swift | 196 +-- .../Collection Common/Cell/NCListCell.xib | 350 +++--- ...nViewCommon+CollectionViewDataSource.swift | 452 +++---- ...ionViewCommon+CollectionViewDelegate.swift | 17 +- .../NCCollectionViewCommon.swift | 1069 +++++------------ .../NCSectionFirstHeader.swift | 263 +--- .../NCSectionFirstHeader.xib | 236 ++-- iOSClient/Menu/NCMenuAction.swift | 121 +- iOSClient/Menu/NCSortMenu.swift | 230 ++-- iOSClient/Networking/NCDownloadAction.swift | 147 +-- iOSClient/Offline/NCOffline.swift | 3 +- 14 files changed, 1385 insertions(+), 2132 deletions(-) diff --git a/iOSClient/Favorites/NCFavorite.swift b/iOSClient/Favorites/NCFavorite.swift index d386b84d5b..cb5bd4cfb6 100644 --- a/iOSClient/Favorites/NCFavorite.swift +++ b/iOSClient/Favorites/NCFavorite.swift @@ -12,7 +12,6 @@ class NCFavorite: NCCollectionViewCommon { titleCurrentFolder = NSLocalizedString("_favorites_", comment: "") layoutKey = NCGlobal.shared.layoutViewFavorite enableSearchBar = false - headerMenuButtonsView = true headerRichWorkspaceDisable = true emptyImageName = "star.fill" emptyImageColors = [NCBrandColor.shared.yellowFavorite] diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index 5b8d99e0a2..c33165f0c9 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -1,25 +1,6 @@ -// -// NCFiles.swift -// Nextcloud -// -// Created by Marino Faggiana on 26/09/2020. -// Copyright © 2020 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2020 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import NextcloudKit @@ -27,18 +8,15 @@ import RealmSwift import SwiftUI class NCFiles: NCCollectionViewCommon { - @IBOutlet weak var plusButton: UIButton! - internal var fileNameBlink: String? internal var fileNameOpen: String? - internal var matadatasHash: String = "" - internal var semaphoreReloadDataSource = DispatchSemaphore(value: 1) - private var timerProcess: Timer? internal var lastOffsetY: CGFloat = 0 internal var lastScrollTime: TimeInterval = 0 internal var accumulatedScrollDown: CGFloat = 0 + internal var syncMetadatasTask: Task? + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) @@ -55,79 +33,79 @@ class NCFiles: NCCollectionViewCommon { override func viewDidLoad() { super.viewDidLoad() - /// Plus Button - let image = UIImage(systemName: "plus", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [.white])) - - plusButton.setTitle("", for: .normal) - plusButton.setImage(image, for: .normal) - plusButton.backgroundColor = NCBrandColor.shared.getElement(account: session.account) - plusButton.accessibilityLabel = NSLocalizedString("_accessibility_add_upload_", comment: "") - plusButton.layer.cornerRadius = plusButton.frame.size.width / 2.0 - plusButton.layer.masksToBounds = false - plusButton.layer.shadowOffset = CGSize(width: 0, height: 0) - plusButton.layer.shadowRadius = 3.0 - plusButton.layer.shadowOpacity = 0.5 - - NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeTheming), object: nil, queue: nil) { _ in - if let activeTableAccount = NCManageDatabase.shared.getActiveTableAccount() { - self.plusButton.backgroundColor = NCBrandColor.shared.getElement(account: activeTableAccount.account) + NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeTheming), object: nil, queue: nil) { notification in + Task { @MainActor in + if let userInfo = notification.userInfo, + let account = userInfo["account"] as? String, + self.controller?.account == account { + let color = NCBrandColor.shared.getElement(account: account) + self.mainNavigationController?.menuToolbar.items?.forEach { $0.tintColor = color } + } } } - - if self.serverUrl.isEmpty { - /// - /// Set ServerURL when start (isEmpty) - /// + NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in + self.stopSyncMetadata() + } + + if self.serverUrl.isEmpty { + // + // Set ServerURL when start (isEmpty) + // self.serverUrl = utilityFileSystem.getHomeServer(session: session) self.titleCurrentFolder = getNavigationTitle() NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeUser), object: nil, queue: nil) { notification in - if let userInfo = notification.userInfo, let account = userInfo["account"] as? String { - if let controller = userInfo["controller"] as? NCMainTabBarController, - controller == self.controller { - controller.account = account - controller.availableNotifications = false - } else { - return + Task { @MainActor in + if let userInfo = notification.userInfo, + let controller = userInfo["controller"] as? NCMainTabBarController { + guard controller == self.controller else { + return + } + } + if let userInfo = notification.userInfo, + let account = userInfo["account"] as? String { + let color = NCBrandColor.shared.getElement(account: account) + self.mainNavigationController?.menuToolbar.items?.forEach { + $0.tintColor = color + } } - } - self.navigationController?.popToRootViewController(animated: false) - self.serverUrl = self.utilityFileSystem.getHomeServer(session: self.session) - self.isSearchingMode = false - self.isEditMode = false - self.fileSelect.removeAll() - self.layoutForView = self.database.getLayoutForView(account: self.session.account, key: self.layoutKey, serverUrl: self.serverUrl) - self.gridLayout.column = CGFloat(self.layoutForView?.columnGrid ?? 3) - - if self.isLayoutList { - self.collectionView?.collectionViewLayout = self.listLayout - } else if self.isLayoutGrid { - self.collectionView?.collectionViewLayout = self.gridLayout - } else if self.isLayoutPhoto { - self.collectionView?.collectionViewLayout = self.mediaLayout - } + self.navigationController?.popToRootViewController(animated: false) + self.serverUrl = self.utilityFileSystem.getHomeServer(session: self.session) + self.isSearchingMode = false + self.isEditMode = false + self.fileSelect.removeAll() + self.layoutForView = self.database.getLayoutForView(account: self.session.account, key: self.layoutKey, serverUrl: self.serverUrl) + + if self.isLayoutList { + self.collectionView?.collectionViewLayout = self.listLayout + } else if self.isLayoutGrid { + self.collectionView?.collectionViewLayout = self.gridLayout + } else if self.isLayoutPhoto { + self.collectionView?.collectionViewLayout = self.mediaLayout + } - self.titleCurrentFolder = self.getNavigationTitle() - ///Magentacloud branding changes hide user account button on left navigation bar -// self.setNavigationLeftItems() + self.titleCurrentFolder = self.getNavigationTitle() + self.navigationItem.title = self.titleCurrentFolder - self.dataSource.removeAll() - self.reloadDataSource() - self.getServerData() + await (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() + await self.reloadDataSource() + await self.getServerData() + } } } - self.timerProcess = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in - self.setNavigationRightItems(enableMenu: false) - }) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - resetPlusButtonAlpha() - reloadDataSource() + Task { + let capabilities = await database.getCapabilities(account: self.session.account) ?? NKCapabilities.Capabilities() + await mainNavigationController?.createPlusMenu(session: self.session, capabilities: capabilities) + + await self.reloadDataSource() + } } override func viewDidAppear(_ animated: Bool) { @@ -149,8 +127,15 @@ class NCFiles: NCCollectionViewCommon { await getServerData() } } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) - self.showTipAutoUpload() + stopSyncMetadata() + Task { + await NCNetworking.shared.networkingTasks.cancel(identifier: "\(self.serverUrl)_NCFiles") + } } override func viewDidDisappear(_ animated: Bool) { @@ -160,34 +145,6 @@ class NCFiles: NCCollectionViewCommon { fileNameOpen = nil } - // MARK: - Action - - @IBAction func plusButtonAction(_ sender: UIButton) { - resetPlusButtonAlpha() - if let controller = UIApplication.shared.firstWindow?.rootViewController as? NCMainTabBarController { - let serverUrl = controller.currentServerUrl() - if let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", NCSession.shared.getSession(controller: controller).account, serverUrl)) { - if !directory.permissions.contains("CK") { - let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_add_file_") - NCContentPresenter().showWarning(error: error) - return - } - } - - let fileFolderPath = NCUtilityFileSystem().getFileNamePath("", serverUrl: serverUrl, session: NCSession.shared.getSession(controller: controller)) - let fileFolderName = (serverUrl as NSString).lastPathComponent - - if !FileNameValidator.checkFolderPath(fileFolderPath, account: controller.account) { - controller.present(UIAlertController.warning(message: "\(String(format: NSLocalizedString("_file_name_validator_error_reserved_name_", comment: ""), fileFolderName)) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) - - return - } - - self.appDelegate.toggleMenu(controller: controller) - } - - } - // MARK: - DataSource override func reloadDataSource() { @@ -238,55 +195,95 @@ class NCFiles: NCCollectionViewCommon { Task { await networking.networkingTasks.cancel(identifier: "\(self.serverUrl)_NCFiles") } + + self.metadataFolder = await self.database.getMetadataFolderAsync(session: self.session, serverUrl: self.serverUrl) + if let tblDirectory = await self.database.getTableDirectoryAsync(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", self.session.account, self.serverUrl)) { + self.richWorkspaceText = tblDirectory.richWorkspace + } + if let metadataFolder { + nkLog(info: "Inside metadata folder \(metadataFolder.fileName) with permissions: \(metadataFolder.permissions)") + + // disable + button if no create permission + let color = NCBrandColor.shared.getElement(account: self.session.account) + + if let items = self.mainNavigationController?.menuToolbar.items { + for item in items { + item.isEnabled = metadataFolder.isCreatable + item.tintColor = metadataFolder.isCreatable ? color : .lightGray + } + } + } + + let metadatas = await self.database.getMetadatasAsyncDataSource(withServerUrl: self.serverUrl, + withUserId: self.session.userId, + withAccount: self.session.account, + withLayout: self.layoutForView) + + self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, + layoutForView: layoutForView, + account: session.account) + await super.reloadDataSource() + + cachingAsync(metadatas: metadatas) + } + + override func getServerData(forced: Bool = false) async { + defer { + restoreDefaultTitle() + startSyncMetadata(metadatas: self.dataSource.getMetadatas()) + } + + Task { + await networking.networkingTasks.cancel(identifier: "\(self.serverUrl)_NCFiles") + } + guard !isSearchingMode else { return networkSearch() } - func downloadMetadata(_ metadata: tableMetadata) -> Bool { - let fileSize = utilityFileSystem.fileProviderStorageSize(metadata.ocId, fileNameView: metadata.fileNameView) + func downloadMetadata(_ metadata: tableMetadata) async -> Bool { + let fileSize = utilityFileSystem.fileProviderStorageSize(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase) guard fileSize > 0 else { return false } - if let localFile = database.getResultsTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))?.first { - if localFile.etag != metadata.etag { + if let tblLocalFile = await database.getTableLocalFileAsync(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) { + if tblLocalFile.etag != metadata.etag { return true } } return false } - DispatchQueue.global().async { - self.networkReadFolder { metadatas, isChanged, error in - DispatchQueue.main.async { - self.refreshControl.endRefreshing() - - if isChanged || self.isNumberOfItemsInAllSectionsNull { - self.reloadDataSource() - } - } + let resultsReadFolder = await networkReadFolderAsync(serverUrl: self.serverUrl, forced: forced) + guard resultsReadFolder.error == .success, resultsReadFolder.reloadRequired else { + return + } - if error == .success { - let metadatas: [tableMetadata] = metadatas ?? self.dataSource.getMetadatas() - for metadata in metadatas where !metadata.directory && downloadMetadata(metadata) { - self.database.setMetadatasSessionInWaitDownload(metadatas: [metadata], - session: NCNetworking.shared.sessionDownload, - selector: NCGlobal.shared.selectorDownloadFile, - sceneIdentifier: self.controller?.sceneIdentifier) - NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true) - } - /// Recommendation - if self.isRecommendationActived { - Task.detached { - await NCNetworking.shared.createRecommendations(session: self.session) - } + let metadatasForDownload: [tableMetadata] = resultsReadFolder.metadatas ?? self.dataSource.getMetadatas() + Task.detached(priority: .utility) { + for metadata in metadatasForDownload where !metadata.directory { + if await downloadMetadata(metadata) { + if let metadata = await self.database.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, + session: NCNetworking.shared.sessionDownload, + selector: NCGlobal.shared.selectorDownloadFile, + sceneIdentifier: self.controller?.sceneIdentifier) { + await NCNetworking.shared.downloadFile(metadata: metadata) } } } } + + await self.reloadDataSource() } - private func networkReadFolder(completion: @escaping (_ metadatas: [tableMetadata]?, _ isDataChanged: Bool, _ error: NKError) -> Void) { - NCNetworking.shared.readFile(serverUrlFileName: serverUrl, account: session.account) { task in - self.dataSourceTask = task + private func networkReadFolderAsync(serverUrl: String, forced: Bool) async -> (metadatas: [tableMetadata]?, error: NKError, reloadRequired: Bool) { + var reloadRequired: Bool = false + let resultsReadFile = await NCNetworking.shared.readFileAsync(serverUrlFileName: serverUrl, account: session.account) { task in + Task { + await NCNetworking.shared.networkingTasks.track(identifier: "\(self.serverUrl)_NCFiles", task: task) + } if self.dataSource.isEmpty() { self.collectionView.reloadData() } @@ -399,11 +396,86 @@ class NCFiles: NCCollectionViewCommon { errorCode: error.errorCode) } } + + guard error == .success else { + return(nil, error, reloadRequired) + } + reloadRequired = true + + if let metadataFolder { + self.metadataFolder = metadataFolder.detachedCopy() + self.richWorkspaceText = metadataFolder.richWorkspace + } + + // + // E2EE section + // + + guard e2eEncrypted, + let metadatas, + !metadatas.isEmpty, + NCPreferences().isEndToEndEnabled(account: account), + await !NCNetworkingE2EE().isInUpload(account: account, serverUrl: serverUrl) else { + return(metadatas, error, reloadRequired) + } + + let lock = await self.database.getE2ETokenLockAsync(account: account, serverUrl: serverUrl) + if let e2eToken = lock?.e2eToken { + nkLog(tag: self.global.logTagE2EE, message: "Tocken: \(e2eToken)", minimumLogLevel: .verbose) + } + + let results = await NCNetworkingE2EE().getMetadata(fileId: ocId, e2eToken: lock?.e2eToken, account: account) + + nkLog(tag: self.global.logTagE2EE, message: "Get metadata with error: \(results.error.errorCode)") + nkLog(tag: self.global.logTagE2EE, message: "Get metadata with metadata: \(results.e2eMetadata ?? ""), signature: \(results.signature ?? ""), version \(results.version ?? "")", minimumLogLevel: .verbose) + + guard results.error == .success, + let e2eMetadata = results.e2eMetadata, + let version = results.version else { + + // No metadata fount, re-send it + if results.error.errorCode == NCGlobal.shared.errorResourceNotFound { + NCContentPresenter().showInfo(description: "Metadata not found") + let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, account: account) + if error != .success { + NCContentPresenter().showError(error: error) + } + } else { + // show error + NCContentPresenter().showError(error: results.error) + } + + return(metadatas, error, reloadRequired) + } + + let errorDecodeMetadata = await NCEndToEndMetadata().decodeMetadata(e2eMetadata, signature: results.signature, serverUrl: serverUrl, session: self.session) + nkLog(debug: "Decode e2ee metadata with error: \(errorDecodeMetadata.errorCode)") + + if errorDecodeMetadata == .success { + let capabilities = await NKCapabilities.shared.getCapabilities(for: self.session.account) + if version == "v1", capabilities.e2EEApiVersion == NCGlobal.shared.e2eeVersionV20 { + NCContentPresenter().showInfo(description: "Conversion metadata v1 to v2 required, please wait...") + nkLog(tag: self.global.logTagE2EE, message: "Conversion v1 to v2") + NCActivityIndicator.shared.start() + + let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, updateVersionV1V2: true, account: account) + if error != .success { + NCContentPresenter().showError(error: error) + } + NCActivityIndicator.shared.stop() + } + } else { + // Client Diagnostic + await self.database.addDiagnosticAsync(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors) + NCContentPresenter().showError(error: error) + } + + return (metadatas, error, reloadRequired) } func blinkCell(fileName: String?) { if let fileName = fileName, let metadata = database.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", session.account, self.serverUrl, fileName)) { - let indexPath = self.dataSource.getIndexPathMetadata(ocId: metadata.ocId).indexPath + let indexPath = self.dataSource.getIndexPathMetadata(ocId: metadata.ocId) if let indexPath = indexPath { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { UIView.animate(withDuration: 0.3) { @@ -423,7 +495,7 @@ class NCFiles: NCCollectionViewCommon { func openFile(fileName: String?) { if let fileName = fileName, let metadata = database.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", session.account, self.serverUrl, fileName)) { - let indexPath = self.dataSource.getIndexPathMetadata(ocId: metadata.ocId).indexPath + let indexPath = self.dataSource.getIndexPathMetadata(ocId: metadata.ocId) if let indexPath = indexPath { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.collectionView(self.collectionView, didSelectItemAt: indexPath) @@ -431,46 +503,25 @@ class NCFiles: NCCollectionViewCommon { } } } - - override func resetPlusButtonAlpha(animated: Bool = true) { - accumulatedScrollDown = 0 - let update = { - self.plusButton.alpha = 1.0 - } - - if animated { - UIView.animate(withDuration: 0.3, animations: update) - } else { - update() - } - } - - override func isHiddenPlusButton(_ isHidden: Bool) { - if isHidden { - UIView.animate(withDuration: 0.5, delay: 0.0, options: [], animations: { - self.plusButton.transform = CGAffineTransform(translationX: 100, y: 0) - self.plusButton.alpha = 0 - }) - } else { - plusButton.transform = CGAffineTransform(translationX: 100, y: 0) - plusButton.alpha = 0 - - UIView.animate(withDuration: 0.5, delay: 0.3, options: [], animations: { - self.plusButton.transform = .identity - self.plusButton.alpha = 1 - }) - } - } // MARK: - NCAccountSettingsModelDelegate - override func accountSettingsDidDismiss(tableAccount: tableAccount?, controller: NCMainTabBarController?) { + override func accountSettingsDidDismiss(tblAccount: tableAccount?, controller: NCMainTabBarController?) { let currentAccount = session.account if database.getAllTableAccount().isEmpty { - if let navigationController = UIStoryboard(name: "NCIntro", bundle: nil).instantiateInitialViewController() as? UINavigationController { - navigationController.modalPresentationStyle = .fullScreen - self.present(navigationController, animated: true) + let navigationController: UINavigationController? + + if NCBrandOptions.shared.disable_intro, let viewController = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLogin") as? NCLogin { + navigationController = UINavigationController(rootViewController: viewController) + } else { + navigationController = UIStoryboard(name: "NCIntro", bundle: nil).instantiateInitialViewController() as? UINavigationController + } + + UIApplication.shared.firstWindow?.rootViewController = navigationController + } else if let account = tblAccount?.account, account != currentAccount { + Task { + await NCAccount().changeAccount(account, userProfile: nil, controller: controller) } UIApplication.shared.mainAppWindow?.rootViewController = navigationController @@ -483,6 +534,8 @@ class NCFiles: NCCollectionViewCommon { navigationItem.title = self.titleCurrentFolder } -// (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() + Task { + await (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() + } } } diff --git a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift index 0d2b1b7e50..b861d54f00 100644 --- a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift +++ b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift @@ -39,13 +39,11 @@ protocol NCCellProtocol { var fileSharedImage: UIImageView? { get set } var fileMoreImage: UIImageView? { get set } var cellSeparatorView: UIView? { get set } - var fileSharedLabel: UILabel? { get set } - var fileProgressView: UIProgressView? { get set } func titleInfoTrailingDefault() func titleInfoTrailingFull() func writeInfoDateSize(date: NSDate, size: Int64) - func setButtonMore(named: String, image: UIImage) + func setButtonMore(image: UIImage) func hideImageItem(_ status: Bool) func hideImageFavorite(_ status: Bool) func hideImageStatus(_ status: Bool) @@ -55,8 +53,6 @@ protocol NCCellProtocol { func hideLabelSubinfo(_ status: Bool) func hideButtonShare(_ status: Bool) func hideButtonMore(_ status: Bool) - func selectMode(_ status: Bool) - func selected(_ status: Bool) func selected(_ status: Bool, isEditMode: Bool) func setAccessibility(label: String, value: String) func setTags(tags: [String]) @@ -119,24 +115,11 @@ extension NCCellProtocol { get { return nil } set {} } - var fileSharedLabel: UILabel? { - get { return nil } - set { } - } - var fileProgressView: UIProgressView? { - get { return nil } - set {} - } - var fileSelectImage: UIImageView? { - get { return nil } - set {} - } - func titleInfoTrailingDefault() {} func titleInfoTrailingFull() {} func writeInfoDateSize(date: NSDate, size: Int64) {} - func setButtonMore(named: String, image: UIImage) {} + func setButtonMore(image: UIImage) {} func hideImageItem(_ status: Bool) {} func hideImageFavorite(_ status: Bool) {} func hideImageStatus(_ status: Bool) {} @@ -146,8 +129,6 @@ extension NCCellProtocol { func hideLabelSubinfo(_ status: Bool) {} func hideButtonShare(_ status: Bool) {} func hideButtonMore(_ status: Bool) {} - func selectMode(_ status: Bool) {} - func selected(_ status: Bool) {} func selected(_ status: Bool, isEditMode: Bool) {} func setAccessibility(label: String, value: String) {} func setTags(tags: [String]) {} diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 19b9be2eb1..5ca11e9bd3 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -28,7 +28,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var imageSelect: UIImageView! @IBOutlet weak var imageStatus: UIImageView! @IBOutlet weak var imageFavorite: UIImageView! - @IBOutlet weak var imageFavoriteBackground: UIImageView! +// @IBOutlet weak var imageFavoriteBackground: UIImageView! @IBOutlet weak var imageLocal: UIImageView! @IBOutlet weak var labelTitle: UILabel! @IBOutlet weak var labelInfo: UILabel! @@ -38,29 +38,23 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var buttonShared: UIButton! @IBOutlet weak var imageMore: UIImageView! @IBOutlet weak var buttonMore: UIButton! - @IBOutlet weak var progressView: UIProgressView! @IBOutlet weak var separator: UIView! - @IBOutlet weak var labelShared: UILabel! @IBOutlet weak var tag0: UILabel! @IBOutlet weak var tag1: UILabel! @IBOutlet weak var imageItemLeftConstraint: NSLayoutConstraint! @IBOutlet weak var separatorHeightConstraint: NSLayoutConstraint! - @IBOutlet weak var subInfoTrailingConstraint: NSLayoutConstraint! - @IBOutlet weak var iPadLabelTitleTrailingConstraint: NSLayoutConstraint! - @IBOutlet weak var iPhoneLabelTitleTrailingConstraint: NSLayoutConstraint! - - private var ocId = "" - private var ocIdTransfer = "" - private var user = "" + @IBOutlet weak var titleTrailingConstraint: NSLayoutConstraint! + + var ocId = "" + var ocIdTransfer = "" + var user = "" weak var listCellDelegate: NCListCellDelegate? - var namedButtonMore = "" var fileAvatarImageView: UIImageView? { return imageShared } - var fileOcId: String? { get { return ocId } set { ocId = newValue ?? "" } @@ -89,14 +83,6 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto get { return labelSubinfo } set { labelSubinfo = newValue } } - var fileProgressView: UIProgressView? { - get { return progressView } - set { progressView = newValue } - } - var fileSelectImage: UIImageView? { - get { return imageSelect } - set { imageSelect = newValue } - } var fileStatusImage: UIImageView? { get { return imageStatus } set { imageStatus = newValue } @@ -122,32 +108,30 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto set { separator = newValue } } - var fileSharedLabel: UILabel? { - get { return labelShared } - set { labelShared = newValue } + override var accessibilityIdentifier: String? { + get { + super.accessibilityIdentifier + } + set { + super.accessibilityIdentifier = newValue + + if let newValue { + buttonShared.accessibilityIdentifier = "\(newValue)/shareButton" + } + } } override func awakeFromNib() { super.awakeFromNib() initCell() } - - func initCell() { - - imageItem.layer.cornerRadius = 6 - imageItem.layer.masksToBounds = true - imageStatus.image = nil - imageFavorite.image = nil - imageFavoriteBackground.isHidden = true - imageLocal.image = nil - labelTitle.text = "" - labelInfo.text = "" - labelSubinfo.text = "" - imageShared.image = nil - imageMore.image = nil - // use entire cell as accessibility element - + override func prepareForReuse() { + super.prepareForReuse() + initCell() + } + + func initCell() { accessibilityHint = nil accessibilityLabel = nil accessibilityValue = nil @@ -158,6 +142,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto imageItem.layer.masksToBounds = true imageStatus.image = nil imageFavorite.image = nil +// imageFavoriteBackground.isHidden = true imageLocal.image = nil labelTitle.text = "" labelInfo.text = "" @@ -168,101 +153,53 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto tag0.text = "" tag1.text = "" titleInfoTrailingDefault() - progressView.tintColor = NCBrandColor.shared.brand - progressView.transform = CGAffineTransform(scaleX: 1.0, y: 0.5) - progressView.trackTintColor = .clear - imageSelect.isHidden = true - - separatorHeightConstraint.constant = 0.5 - titleInfoTrailingDefault() let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(gestureRecognizer:))) longPressedGesture.minimumPressDuration = 0.5 longPressedGesture.delegate = self longPressedGesture.delaysTouchesBegan = true self.addGestureRecognizer(longPressedGesture) - - separator.backgroundColor = .separator - separatorHeightConstraint.constant = 0.5 - titleInfoTrailingDefault() - - labelTitle.textColor = .label - labelInfo.textColor = .systemGray - labelSubinfo.textColor = .systemGray - setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCImageCache.images.buttonMore) - imageMore.isHidden = false - buttonMore.isHidden = false - updateConstraintsForCurrentDevice() - } - - override func prepareForReuse() { - super.prepareForReuse() - initCell() } override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? { return nil } - - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - updateConstraintsForCurrentDevice() - } - func updateConstraintsForCurrentDevice() { - if labelShared?.isHidden == false { - iPhoneLabelTitleTrailingConstraint.isActive = false - iPadLabelTitleTrailingConstraint.isActive = true - } else { - iPhoneLabelTitleTrailingConstraint.isActive = true - iPadLabelTitleTrailingConstraint.isActive = false - } -// iPhoneLabelTitleTrailingConstraint.isActive = UIDevice.current.userInterfaceIdiom == .pad ? false : true -// iPadLabelTitleTrailingConstraint.isActive = UIDevice.current.userInterfaceIdiom == .pad ? true : false - } - @IBAction func touchUpInsideShare(_ sender: Any) { listCellDelegate?.tapShareListItem(with: ocId, ocIdTransfer: ocIdTransfer, sender: sender) } @IBAction func touchUpInsideMore(_ sender: Any) { - listCellDelegate?.tapMoreListItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: imageItem.image, sender: sender) + listCellDelegate?.tapMoreListItem(with: ocId, ocIdTransfer: ocIdTransfer, image: imageItem.image, sender: sender) } @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { - listCellDelegate?.longPressListItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, gestureRecognizer: gestureRecognizer) - } - - @objc func longPressInsideMore(gestureRecognizer: UILongPressGestureRecognizer) { - listCellDelegate?.longPressMoreListItem(with: ocId, namedButtonMore: namedButtonMore, gestureRecognizer: gestureRecognizer) + listCellDelegate?.longPressListItem(with: ocId, ocIdTransfer: ocIdTransfer, gestureRecognizer: gestureRecognizer) } fileprivate func setA11yActions() { - let moreName = namedButtonMore == NCGlobal.shared.buttonMoreStop ? "_cancel_" : "_more_" self.accessibilityCustomActions = [ UIAccessibilityCustomAction( name: NSLocalizedString("_share_", comment: ""), target: self, - selector: #selector(touchUpInsideShare)), + selector: #selector(touchUpInsideShare(_:))), UIAccessibilityCustomAction( - name: NSLocalizedString(moreName, comment: ""), + name: NSLocalizedString("_more_", comment: ""), target: self, - selector: #selector(touchUpInsideMore)) + selector: #selector(touchUpInsideMore(_:))) ] } func titleInfoTrailingFull() { - subInfoTrailingConstraint.constant = 10 + titleTrailingConstraint.constant = 10 } func titleInfoTrailingDefault() { - subInfoTrailingConstraint.constant = 90 + titleTrailingConstraint.constant = 90 } - func setButtonMore(named: String, image: UIImage) { - namedButtonMore = named + func setButtonMore(image: UIImage) { imageMore.image = image - setA11yActions() } @@ -276,68 +213,58 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto buttonShared.isHidden = status } - func hideSeparator(_ status: Bool) { - separator.isHidden = status - } - - func selectMode(_ status: Bool) { - if status { + func selected(_ status: Bool, isEditMode: Bool) { + // E2EE - remove encrypt folder selection + if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId), metadata.e2eEncrypted { + imageSelect.isHidden = true + } else { + imageSelect.isHidden = isEditMode ? false : true + } +// guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId), !metadata.e2eEncrypted else { +//// guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId), !metadata.e2eEncrypted else { +// backgroundView = nil +// separator.isHidden = false +// imageSelect.isHidden = true +// return +// } + if isEditMode { imageItemLeftConstraint.constant = 45 - imageSelect.isHidden = false +// imageSelect.isHidden = false + imageShared.isHidden = true imageMore.isHidden = true buttonShared.isHidden = true buttonMore.isHidden = true accessibilityCustomActions = nil } else { imageItemLeftConstraint.constant = 10 - imageSelect.isHidden = true +// imageSelect.isHidden = true + imageShared.isHidden = false imageMore.isHidden = false buttonShared.isHidden = false buttonMore.isHidden = false backgroundView = nil setA11yActions() } - } - - func selected(_ status: Bool, isEditMode: Bool) { - // NMC-1190 - iOS - Files - Deleting files while files are still uploading won't delete properly : to fix this issue remove check for !metadata.isInTransfer in below line - guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId), !metadata.isInTransfer, !metadata.e2eEncrypted else { -// guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId), !metadata.e2eEncrypted else { - backgroundView = nil - separator.isHidden = false - imageSelect.isHidden = true - - return - } - if status { - var blurEffect: UIVisualEffect? var blurEffectView: UIView? - if traitCollection.userInterfaceStyle == .dark { - blurEffect = UIBlurEffect(style: .dark) - blurEffectView = UIVisualEffectView(effect: blurEffect) - blurEffectView?.backgroundColor = .black - } else { - blurEffect = UIBlurEffect(style: .extraLight) - blurEffectView = UIVisualEffectView(effect: blurEffect) - blurEffectView?.backgroundColor = .lightGray - } + blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial)) + blurEffectView?.backgroundColor = .lightGray blurEffectView?.frame = self.bounds blurEffectView?.autoresizingMask = [.flexibleWidth, .flexibleHeight] - imageSelect.image = NCImageCache.images.checkedYes + imageSelect.image = NCImageCache.shared.getImageCheckedYes() backgroundView = blurEffectView - imageSelect.image = NCImageCache.images.checkedYes separator.isHidden = true } else { - imageSelect.image = NCImageCache.images.checkedNo + imageSelect.image = NCImageCache.shared.getImageCheckedNo() backgroundView = nil separator.isHidden = false } + } func writeInfoDateSize(date: NSDate, size: Int64) { - labelInfo.text = NCUtility().getRelativeDateTitle(date as Date) + " · " + NCUtilityFileSystem().transformedSize(size) - labelSubinfo.text = "" + labelInfo.text = NCUtility().getRelativeDateTitle(date as Date) + labelSubinfo.text = NCUtilityFileSystem().transformedSize(size) } func setAccessibility(label: String, value: String) { @@ -396,7 +323,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } } - + override func layoutSubviews() { super.layoutSubviews() // Keep the shadow path in sync with current bounds @@ -411,9 +338,8 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto protocol NCListCellDelegate: AnyObject { func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) - func tapMoreListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) - func longPressMoreListItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) - func longPressListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) + func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) + func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) } // MARK: - List Layout diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.xib b/iOSClient/Main/Collection Common/Cell/NCListCell.xib index e7b2e84c4e..21e1dbbccb 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.xib @@ -1,218 +1,258 @@ - + - - + - - + + - + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + + diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift index fe862508db..405aad72ed 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift @@ -13,15 +13,18 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - let numberItems = dataSource.numberOfItemsInSection(section) - emptyDataSet?.numberOfItemsInSection(numberItems, section: section) // get auto upload folder - self.autoUploadFileName = self.database.getAccountAutoUploadFileName(account: self.session.account) - self.autoUploadDirectory = self.database.getAccountAutoUploadDirectory(session: self.session) + self.autoUploadFileName = self.database.getAccountAutoUploadFileName(account: session.account) + self.autoUploadDirectory = self.database.getAccountAutoUploadDirectory(account: session.account, urlBase: session.urlBase, userId: session.userId) // get layout for view - self.layoutForView = self.database.getLayoutForView(account: self.session.account, key: self.layoutKey, serverUrl: self.serverUrl) - - return numberItems + self.layoutForView = self.database.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) + // is a Directory E2EE + if isSearchingMode { + self.isDirectoryE2EE = false + } else { + self.isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(serverUrl: serverUrl, urlBase: session.urlBase, userId: session.userId, account: session.account) + } + return self.dataSource.numberOfItemsInSection(section) } func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { @@ -40,7 +43,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { guard let metadata = self.dataSource.getMetadata(indexPath: indexPath) else { return } - let existsImagePreview = self.utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag) + let existsImagePreview = self.utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag, userId: metadata.userId, urlBase: metadata.urlBase) let ext = self.global.getSizeExtension(column: self.numberOfColumns) if metadata.hasPreview, @@ -59,8 +62,8 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.hideButtonMore(true) cell.hideImageStatus(true) - /// Image - /// + // Image + // if let image = NCImageCache.shared.getImageCache(ocId: metadata.ocId, etag: metadata.etag, ext: ext) { cell.filePreviewImageView?.image = image @@ -69,11 +72,11 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } else { if isPinchGestureActive || ext == global.previewExt512 || ext == global.previewExt1024 { - cell.filePreviewImageView?.image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext) + cell.filePreviewImageView?.image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext, userId: metadata.userId, urlBase: metadata.urlBase) } DispatchQueue.global(qos: .userInteractive).async { - let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext) + let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext, userId: metadata.userId, urlBase: metadata.urlBase) if let image { self.imageCache.addImageCache(ocId: metadata.ocId, etag: metadata.etag, image: image, ext: ext, cost: indexPath.row) DispatchQueue.main.async { @@ -93,20 +96,11 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } } - /// Status - /// - if metadata.isLivePhoto { - cell.fileStatusImage?.image = utility.loadImage(named: "livephoto", colors: isLayoutPhoto ? [.white] : [NCBrandColor.shared.iconImageColor2]) - } else if metadata.isVideo { - cell.fileStatusImage?.image = utility.loadImage(named: "play.circle", colors: NCBrandColor.shared.iconImageMultiColors) - } - - /// Edit mode + // Edit mode + // if fileSelect.contains(metadata.ocId) { - cell.selectMode(true) cell.selected(true, isEditMode: isEditMode) } else { - cell.selectMode(false) cell.selected(false, isEditMode: isEditMode) } @@ -120,46 +114,53 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { var cell: NCCellProtocol & UICollectionViewCell - let permissions = NCPermissions() var isShare = false var isMounted = false var a11yValues: [String] = [] - - // LAYOUT PHOTO - if layoutForView?.layout == NCGlobal.shared.layoutPhotoRatio || layoutForView?.layout == NCGlobal.shared.layoutPhotoSquare { - guard let photoCell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as? NCPhotoCell else { return NCPhotoCell() } - photoCell.photoCellDelegate = self - cell = photoCell - } else if layoutForView?.layout == NCGlobal.shared.layoutGrid { - // LAYOUT GRID - guard let gridCell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridCell else { return NCGridCell() } - gridCell.gridCellDelegate = self - cell = gridCell - } else { - // LAYOUT LIST - guard let listCell = collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as? NCListCell else { return NCListCell() } - listCell.listCellDelegate = self - cell = listCell - } - guard let metadata = dataSource.cellForItemAt(indexPath: indexPath) else { return cell } - let shares = NCManageDatabase.shared.getTableShares(metadata: metadata) - let existsImagePreview = utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag) + let metadata = self.dataSource.getMetadata(indexPath: indexPath) ?? tableMetadata() + let existsImagePreview = utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag, userId: metadata.userId, urlBase: metadata.urlBase) let ext = global.getSizeExtension(column: self.numberOfColumns) + let shares = NCManageDatabase.shared.getTableShares(metadata: metadata) defer { + let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() if !metadata.isSharable() || (!capabilities.fileSharingApiEnabled && !capabilities.filesComments && capabilities.activity.isEmpty) { cell.hideButtonShare(true) } } // E2EE create preview - if self.isDirectoryEncrypted, + if self.isDirectoryE2EE, metadata.isImageOrVideo, - !utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag) { + !utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag, userId: metadata.userId, urlBase: metadata.urlBase) { utility.createImageFileFrom(metadata: metadata) } - /// CONTENT MODE + // LAYOUT PHOTO + if isLayoutPhoto { + if metadata.isImageOrVideo { + let photoCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as? NCPhotoCell)! + photoCell.photoCellDelegate = self + cell = photoCell + return self.photoCell(cell: photoCell, indexPath: indexPath, metadata: metadata, ext: ext) + } else { + let gridCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridCell)! + gridCell.gridCellDelegate = self + cell = gridCell + } + } else if isLayoutGrid { + // LAYOUT GRID + let gridCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridCell)! + gridCell.gridCellDelegate = self + cell = gridCell + } else { + // LAYOUT LIST + let listCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as? NCListCell)! + listCell.listCellDelegate = self + cell = listCell + } + + // CONTENT MODE cell.fileAvatarImageView?.contentMode = .center cell.filePreviewImageView?.layer.borderWidth = 0 @@ -174,59 +175,33 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } if metadataFolder != nil { - isShare = metadata.permissions.contains(permissions.permissionShared) && !metadataFolder!.permissions.contains(permissions.permissionShared) - isMounted = metadata.permissions.contains(permissions.permissionMounted) && !metadataFolder!.permissions.contains(permissions.permissionMounted) + isShare = metadata.permissions.contains(NCMetadataPermissions.permissionShared) && !metadataFolder!.permissions.contains(NCMetadataPermissions.permissionShared) + isMounted = metadata.permissions.contains(NCMetadataPermissions.permissionMounted) && !metadataFolder!.permissions.contains(NCMetadataPermissions.permissionMounted) } cell.fileAccount = metadata.account cell.fileOcId = metadata.ocId cell.fileOcIdTransfer = metadata.ocIdTransfer cell.fileUser = metadata.ownerId - cell.fileSelectImage?.image = nil - cell.fileStatusImage?.image = nil - cell.fileLocalImage?.image = nil - cell.fileFavoriteImage?.image = nil - cell.fileMoreImage?.image = nil - cell.filePreviewImageView?.image = nil - cell.filePreviewImageView?.backgroundColor = nil - cell.fileProgressView?.isHidden = true - cell.fileProgressView?.progress = 0.0 - cell.hideButtonShare(false) - cell.hideButtonMore(false) - cell.titleInfoTrailingDefault() - + if isSearchingMode { - cell.fileTitleLabel?.text = metadata.fileName - cell.fileTitleLabel?.lineBreakMode = .byTruncatingTail if metadata.name == global.appName { - cell.fileInfoLabel?.text = utility.dateDiff(metadata.date as Date) + " · " + utilityFileSystem.transformedSize(metadata.size) + cell.fileInfoLabel?.text = NSLocalizedString("_in_", comment: "") + " " + utilityFileSystem.getPath(path: metadata.path, user: metadata.user) } else { cell.fileInfoLabel?.text = metadata.subline } cell.fileSubinfoLabel?.isHidden = true } else if !metadata.sessionError.isEmpty, metadata.status != global.metadataStatusNormal { - cell.fileTitleLabel?.text = metadata.fileNameView cell.fileSubinfoLabel?.isHidden = false cell.fileInfoLabel?.text = metadata.sessionError -// // Temporary issue fix for NMC-3771: iOS v9.1.6 > multiple uploads cause error messages -// if metadata.sessionError == "423: WebDAV Locked: Trying to access locked resource" || metadata.sessionError == "423: WebDAV gesperrt: Zugriffsversuch auf eine gesperrte Ressource" { -// cell.fileTitleLabel?.text = metadata.fileName -// cell.fileTitleLabel?.lineBreakMode = .byTruncatingMiddle -// } else { -// cell.fileSubinfoLabel?.isHidden = false -// cell.fileInfoLabel?.text = metadata.sessionError -// } } else { cell.fileSubinfoLabel?.isHidden = false - cell.fileTitleLabel?.text = metadata.fileNameView - cell.fileTitleLabel?.lineBreakMode = .byTruncatingMiddle + cell.writeInfoDateSize(date: metadata.date, size: metadata.size) } - if metadata.status == NCGlobal.shared.metadataStatusDownloading || metadata.status == NCGlobal.shared.metadataStatusUploading { - cell.fileProgressView?.isHidden = false - } - + cell.fileTitleLabel?.text = metadata.fileNameView + // Accessibility [shared] if metadata.ownerId != appDelegate.userId, appDelegate.account == metadata.account { if metadata.ownerId != metadata.userId { a11yValues.append(NSLocalizedString("_shared_with_you_by_", comment: "") + " " + metadata.ownerDisplayName) @@ -246,13 +221,13 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } else if !metadata.shareType.isEmpty && metadata.shareType.contains(NKShare.ShareType.publicLink.rawValue) { cell.filePreviewImageView?.image = imageCache.getFolderPublic(account: metadata.account) } else if metadata.mountType == "group" { - cell.filePreviewImageView?.image = imageCache.getFolderGroup() + cell.filePreviewImageView?.image = imageCache.getFolderGroup(account: metadata.account) } else if isMounted { - cell.filePreviewImageView?.image = imageCache.getFolderExternal() + cell.filePreviewImageView?.image = imageCache.getFolderExternal(account: metadata.account) } else if metadata.fileName == autoUploadFileName && metadata.serverUrl == autoUploadDirectory { - cell.filePreviewImageView?.image = imageCache.getFolderAutomaticUpload() + cell.filePreviewImageView?.image = imageCache.getFolderAutomaticUpload(account: metadata.account) } else { - cell.filePreviewImageView?.image = imageCache.getFolder() + cell.filePreviewImageView?.image = imageCache.getFolder(account: metadata.account) } // Local image: offline @@ -274,9 +249,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if metadata.name == global.appName { if let image = NCImageCache.shared.getImageCache(ocId: metadata.ocId, etag: metadata.etag, ext: ext) { cell.filePreviewImageView?.image = image - } else if metadata.fileExtension == "odg" { - cell.filePreviewImageView?.image = UIImage(named: "diagram") - } else if let image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext) { + } else if let image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext, userId: metadata.userId, urlBase: metadata.urlBase) { cell.filePreviewImageView?.image = image } @@ -288,40 +261,45 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } } } else { - /// APP NAME - UNIFIED SEARCH + // APP NAME - UNIFIED SEARCH switch metadata.iconName { case let str where str.contains("contacts"): - cell.filePreviewImageView?.image = NCImageCache.images.iconContacts + cell.filePreviewImageView?.image = utility.loadImage(named: "person.crop.rectangle.stack", colors: [NCBrandColor.shared.iconImageColor]) case let str where str.contains("conversation"): - cell.filePreviewImageView?.image = NCImageCache.images.iconTalk + cell.filePreviewImageView?.image = UIImage(named: "talk-template")!.image(color: NCBrandColor.shared.getElement(account: metadata.account)) case let str where str.contains("calendar"): - cell.filePreviewImageView?.image = NCImageCache.images.iconCalendar + cell.filePreviewImageView?.image = utility.loadImage(named: "calendar", colors: [NCBrandColor.shared.iconImageColor]) case let str where str.contains("deck"): - cell.filePreviewImageView?.image = NCImageCache.images.iconDeck + cell.filePreviewImageView?.image = utility.loadImage(named: "square.stack.fill", colors: [NCBrandColor.shared.iconImageColor]) case let str where str.contains("mail"): - cell.filePreviewImageView?.image = NCImageCache.images.iconMail + cell.filePreviewImageView?.image = utility.loadImage(named: "mail", colors: [NCBrandColor.shared.iconImageColor]) case let str where str.contains("talk"): - cell.filePreviewImageView?.image = NCImageCache.images.iconTalk + cell.filePreviewImageView?.image = UIImage(named: "talk-template")!.image(color: NCBrandColor.shared.getElement(account: metadata.account)) case let str where str.contains("confirm"): - cell.filePreviewImageView?.image = NCImageCache.images.iconConfirm + cell.filePreviewImageView?.image = utility.loadImage(named: "arrow.right", colors: [NCBrandColor.shared.iconImageColor]) case let str where str.contains("pages"): - cell.filePreviewImageView?.image = NCImageCache.images.iconPages + cell.filePreviewImageView?.image = utility.loadImage(named: "doc.richtext", colors: [NCBrandColor.shared.iconImageColor]) default: - cell.filePreviewImageView?.image = NCImageCache.images.file + cell.filePreviewImageView?.image = utility.loadImage(named: "doc", colors: [NCBrandColor.shared.iconImageColor]) } if !metadata.iconUrl.isEmpty { if let ownerId = getAvatarFromIconUrl(metadata: metadata) { let fileName = NCSession.shared.getFileName(urlBase: metadata.urlBase, user: ownerId) - self.database.getImageAvatarLoaded(fileName: fileName) { image, tblAvatar in - if let image { - cell.filePreviewImageView?.image = image - } else { - cell.filePreviewImageView?.image = self.utility.loadUserImage(for: ownerId, displayName: nil, urlBase: metadata.urlBase) - } - - if !(tblAvatar?.loaded ?? false), - self.networking.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty { - self.networking.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: ownerId, fileName: fileName, account: metadata.account, view: collectionView, isPreviewImageView: true)) + if let image = NCImageCache.shared.getImageCache(key: fileName) { + cell.filePreviewImageView?.image = image + } else { + self.database.getImageAvatarLoaded(fileName: fileName) { image, tblAvatar in + if let image { + cell.filePreviewImageView?.image = image + NCImageCache.shared.addImageCache(image: image, key: fileName) + } else { + cell.filePreviewImageView?.image = self.utility.loadUserImage(for: ownerId, displayName: nil, urlBase: metadata.urlBase) + } + + if !(tblAvatar?.loaded ?? false), + self.networking.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty { + self.networking.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: ownerId, fileName: fileName, account: metadata.account, view: collectionView, isPreviewImageView: true)) + } } } } @@ -341,7 +319,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.fileFavoriteImage?.image = imageCache.getImageFavorite() a11yValues.append(NSLocalizedString("_favorite_short_", comment: "")) } - + // Share image if isShare { cell.fileSharedImage?.image = imageCache.getImageShared() @@ -350,45 +328,36 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { (cell.fileSharedImage?.image = imageCache.getImageShareByLink()) : (cell.fileSharedImage?.image = imageCache.getImageShared()) } else { - cell.fileSharedImage?.image = NCImageCache.images.canShare.image(color: NCBrandColor.shared.gray60) - cell.fileSharedLabel?.text = "" + cell.fileSharedImage?.image = NCImageCache.shared.getImageCanShare().image(color: NCBrandColor.shared.gray60) } - if appDelegate.account != metadata.account { - cell.fileSharedImage?.image = NCImageCache.images.shared + if session.account != metadata.account { + cell.fileSharedImage?.image = imageCache.getImageShared() } - cell.fileSharedLabel?.text = NSLocalizedString("_shared_", comment: "") - cell.fileSharedLabel?.textColor = NCBrandColor.shared.customer if (!metadata.shareType.isEmpty || !(shares.share?.isEmpty ?? true) || (shares.firstShareLink != nil)){ - cell.fileSharedImage?.image = cell.fileSharedImage?.image?.imageColor(NCBrandColor.shared.customer) + cell.fileSharedImage?.image = cell.fileSharedImage?.image?.image(color: NCBrandColor.shared.customer) } else { - cell.fileSharedImage?.image = NCImageCache.images.canShare.image(color: NCBrandColor.shared.gray60) - cell.fileSharedLabel?.text = "" + cell.fileSharedImage?.image = NCImageCache.shared.getImageCanShare().image(color: NCBrandColor.shared.gray60) } if metadata.permissions.contains("S"), (metadata.permissions.range(of: "S") != nil) { - cell.fileSharedImage?.image = NCImageCache.images.sharedWithMe - cell.fileSharedLabel?.text = NSLocalizedString("_recieved_", comment: "") - cell.fileSharedLabel?.textColor = NCBrandColor.shared.notificationAction + cell.fileSharedImage?.image = NCImageCache.shared.getImageSharedWithMe() } // Button More - if metadata.isInTransfer || metadata.isWaitingTransfer { - cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCImageCache.images.buttonStop) - } else if metadata.lock == true { - cell.setButtonMore(named: NCGlobal.shared.buttonMoreLock, image: NCImageCache.images.buttonMoreLock) + if metadata.lock == true { + cell.setButtonMore(image: imageCache.getImageButtonMoreLock()) a11yValues.append(String(format: NSLocalizedString("_locked_by_", comment: ""), metadata.lockOwnerDisplayName)) } else { - cell.setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCImageCache.images.buttonMore) + cell.setButtonMore(image: imageCache.getImageButtonMore()) } - // Status + // Staus if metadata.isLivePhoto { - cell.fileStatusImage?.image = utility.loadImage(named: "livephoto", colors: [NCBrandColor.shared.iconImageColor]) + cell.fileStatusImage?.image = utility.loadImage(named: "livephoto", colors: isLayoutPhoto ? [.white] : [NCBrandColor.shared.iconImageColor2]) a11yValues.append(NSLocalizedString("_upload_mov_livephoto_", comment: "")) } else if metadata.isVideo { cell.fileStatusImage?.image = utility.loadImage(named: "play.circle.fill", colors: [.systemBackgroundInverted, .systemGray5]) } - switch metadata.status { case global.metadataStatusWaitCreateFolder: cell.fileStatusImage?.image = utility.loadImage(named: "arrow.triangle.2.circlepath", colors: NCBrandColor.shared.iconImageMultiColors) @@ -408,9 +377,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { case global.metadataStatusWaitDownload: cell.fileStatusImage?.image = utility.loadImage(named: "arrow.triangle.2.circlepath", colors: NCBrandColor.shared.iconImageMultiColors) case global.metadataStatusDownloading: - if #available(iOS 17.0, *) { - cell.fileStatusImage?.image = utility.loadImage(named: "arrowshape.down.circle", colors: NCBrandColor.shared.iconImageMultiColors) - } + cell.fileStatusImage?.image = utility.loadImage(named: "arrowshape.down.circle", colors: NCBrandColor.shared.iconImageMultiColors) case global.metadataStatusDownloadError, global.metadataStatusUploadError: cell.fileStatusImage?.image = utility.loadImage(named: "exclamationmark.circle", colors: NCBrandColor.shared.iconImageMultiColors) default: @@ -435,16 +402,11 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } // Edit mode - if isEditMode { - cell.selectMode(true) - if fileSelect.contains(metadata.ocId) { - cell.selected(true, isEditMode: isEditMode) - a11yValues.append(NSLocalizedString("_selected_", comment: "")) - } else { - cell.selected(false, isEditMode: isEditMode) - } + if fileSelect.contains(metadata.ocId) { + cell.selected(true, isEditMode: isEditMode) + a11yValues.append(NSLocalizedString("_selected_", comment: "")) } else { - cell.selectMode(false) + cell.selected(false, isEditMode: isEditMode) } // Accessibility @@ -464,6 +426,33 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { // TAGS cell.setTags(tags: Array(metadata.tags)) + // Layout photo + if isLayoutPhoto { + let width = UIScreen.main.bounds.width / CGFloat(self.numberOfColumns) + + cell.hideImageFavorite(false) + cell.hideImageLocal(false) + cell.hideImageItem(false) + cell.hideButtonMore(false) + cell.hideLabelInfo(false) + cell.hideLabelSubinfo(false) + cell.hideImageStatus(false) + cell.fileTitleLabel?.font = UIFont.systemFont(ofSize: 15) + + if width < 120 { + cell.hideImageFavorite(true) + cell.hideImageLocal(true) + cell.fileTitleLabel?.font = UIFont.systemFont(ofSize: 10) + if width < 100 { + cell.hideImageItem(true) + cell.hideButtonMore(true) + cell.hideLabelInfo(true) + cell.hideLabelSubinfo(true) + cell.hideImageStatus(true) + } + } + } + // Hide buttons if metadata.name != global.appName { cell.titleInfoTrailingFull() @@ -471,28 +460,18 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.hideButtonMore(true) } - cell.setIconOutlines() - - // Hide lines on iPhone - if !UIDevice.current.orientation.isLandscape && UIDevice.current.model.hasPrefix("iPhone") { - cell.cellSeparatorView?.isHidden = true - cell.fileSharedLabel?.isHidden = true - }else{ - cell.cellSeparatorView?.isHidden = false - cell.fileSharedLabel?.isHidden = false - } return cell } func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + func setContent(header: UICollectionReusableView, indexPath: IndexPath) { + let (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) = getHeaderHeight(section: indexPath.section) - if kind == UICollectionView.elementKindSectionHeader { + if let header = header as? NCSectionFirstHeader { + let recommendations = self.database.getRecommendedFiles(account: self.session.account) + var sectionText = NSLocalizedString("_all_files_", comment: "") - if indexPath.section == 0 { - guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionHeaderMenu", for: indexPath) as? NCSectionHeaderMenu else { return UICollectionReusableView() } - let (_, heightHeaderRichWorkspace, heightHeaderSection) = getHeaderHeight(section: indexPath.section) - - if NCKeychain().getPersonalFilesOnly(account: session.account) { + if NCPreferences().getPersonalFilesOnly(account: session.account) { sectionText = NSLocalizedString("_personal_files_", comment: "") } @@ -516,7 +495,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if isSearchingMode { emptyImage = utility.loadImage(named: "magnifyingglass", colors: [NCBrandColor.shared.getElement(account: session.account)]) - if self.dataSourceTask?.state == .running { + if self.searchDataSourceTask?.state == .running { emptyTitle = NSLocalizedString("_search_in_progress_", comment: "") } else { emptyTitle = NSLocalizedString("_search_no_record_found_", comment: "") @@ -539,6 +518,10 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { emptyImage = utility.loadImage(named: "arrow.triangle.2.circlepath", colors: [NCBrandColor.shared.getElement(account: session.account)]) emptyTitle = NSLocalizedString("_files_no_files_", comment: "") emptyDescription = NSLocalizedString("_folder_offline_desc_", comment: "") + } else if let metadataFolder, !metadataFolder.isCreatable { + emptyImage = imageCache.getFolder(account: session.account) + emptyTitle = NSLocalizedString("_files_no_files_", comment: "") + emptyDescription = NSLocalizedString("_no_file_no_permission_to_create_", comment: "") } else { emptyImage = imageCache.getFolder(account: session.account) emptyTitle = NSLocalizedString("_files_no_files_", comment: "") @@ -571,45 +554,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionFirstHeader", for: indexPath) as? NCSectionFirstHeader else { return NCSectionFirstHeader() } self.sectionFirstHeader = header -// setContent(header: header, indexPath: indexPath) -// self.headerMenu = header -// self.headerMenu?.setViewTransfer(isHidden: true) - if layoutForView?.layout == NCGlobal.shared.layoutGrid { - header.setImageSwitchList() - header.buttonSwitch.accessibilityLabel = NSLocalizedString("_list_view_", comment: "") - } else { - header.setImageSwitchGrid() - header.buttonSwitch.accessibilityLabel = NSLocalizedString("_grid_view_", comment: "") - } - - header.delegate = self - - if !isSearchingMode, headerMenuTransferView, isHeaderMenuTransferViewEnabled() != nil, let ocId = NCNetworking.shared.transferInForegorund?.ocId { - let text = String(format: NSLocalizedString("_upload_foreground_msg_", comment: ""), NCBrandOptions.shared.brand) -// header.setViewTransfer(isHidden: false, text: text) - header.setViewTransfer(isHidden: false, ocId: ocId, text: text, progress: NCNetworking.shared.transferInForegorund?.progress) - } else { - header.setViewTransfer(isHidden: true) - } - - if headerMenuButtonsView { - header.setStatusButtonsView(enable: !dataSource.isEmpty()) - header.setButtonsView(height: NCGlobal.shared.heightButtonsView) - header.setSortedTitle(layoutForView?.titleButtonHeader ?? "") - } else { - header.setButtonsView(height: 0) - } - - header.setRichWorkspaceHeight(heightHeaderRichWorkspace) - header.setRichWorkspaceText(richWorkspaceText) - - header.setSectionHeight(heightHeaderSection) - if heightHeaderSection == 0 { - header.labelSection.text = "" - } else { - header.labelSection.text = self.dataSource.getSectionValueLocalization(indexPath: indexPath) - } - header.labelSection.textColor = NCBrandColor.shared.textColor + setContent(header: header, indexPath: indexPath) return header @@ -634,16 +579,10 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { footer.setTitleLabel("") footer.setButtonText(NSLocalizedString("_show_more_results_", comment: "")) - footer.buttonSection.setTitleColor(NCBrandColor.shared.customer, for: .normal) - footer.separatorIsHidden(true) footer.buttonIsHidden(true) footer.hideActivityIndicatorSection() if isSearchingMode { - if sections > 1 && section != sections - 1 { - footer.separatorIsHidden(false) - } - // If the number of entries(metadatas) is lower than the cursor, then there are no more entries. // 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. // This is as good as it gets for determining last page without server-side flag. @@ -656,26 +595,21 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if unifiedSearchInProgress { footer.showActivityIndicatorSection() } + } else if isEditMode { + // let itemsSelected = self.fileSelect.count + // let items = self.dataSource.numberOfItemsInSection(section) + // footer.setTitleLabel("\(itemsSelected) \(NSLocalizedString("_of_", comment: "")) \(items) \(NSLocalizedString("_selected_", comment: ""))") + footer.setTitleLabel("") } else { if sections == 1 || section == sections - 1 { let info = self.dataSource.getFooterInformation() footer.setTitleLabel(directories: info.directories, files: info.files, size: info.size) - } else { - footer.separatorIsHidden(false) } } return footer } } - func setContent(header: UICollectionReusableView, indexPath: IndexPath) { - if let header = header as? NCSectionHeader { - let text = self.dataSource.getSectionValueLocalization(indexPath: indexPath) - - header.setContent(text: text) - } - } - // MARK: - func getAvatarFromIconUrl(metadata: tableMetadata) -> String? { @@ -714,104 +648,12 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { // caching preview // - if let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: self.global.previewExt256) { + if let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: self.global.previewExt256, userId: metadata.userId, urlBase: metadata.urlBase) { NCImageCache.shared.addImageCache(ocId: metadata.ocId, etag: metadata.etag, image: image, ext: self.global.previewExt256, cost: cost) } } } } - - // MARK: - Cancel (Download Upload) - - // sessionIdentifierDownload: String = "com.nextcloud.nextcloudkit.session.download" - // sessionIdentifierUpload: String = "com.nextcloud.nextcloudkit.session.upload" - - // sessionUploadBackground: String = "com.nextcloud.session.upload.background" - // sessionUploadBackgroundWWan: String = "com.nextcloud.session.upload.backgroundWWan" - // sessionUploadBackgroundExtension: String = "com.nextcloud.session.upload.extension" - - func cancelSession(metadata: tableMetadata) async { - - let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView) - utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId)) - NCManageDatabase.shared.deleteLocalFileOcId(metadata.ocId) - - // No session found - if metadata.session.isEmpty { - NCNetworking.shared.uploadRequest.removeValue(forKey: fileNameLocalPath) - NCNetworking.shared.downloadRequest.removeValue(forKey: fileNameLocalPath) - NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) - return - } - - // DOWNLOAD FOREGROUND - if metadata.session == NextcloudKit.shared.nkCommonInstance.identifierSessionDownload { - if let request = NCNetworking.shared.downloadRequest[fileNameLocalPath] { - request.cancel() - } else if let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) { - NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId, - session: "", - sessionError: "", - selector: "", - status: NCGlobal.shared.metadataStatusNormal) - NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadCancelFile), - object: nil, - userInfo: ["ocId": metadata.ocId, - "serverUrl": metadata.serverUrl, - "account": metadata.account]) - } - return - } - - // DOWNLOAD BACKGROUND - if metadata.session == NCNetworking.shared.sessionDownloadBackground { - let session: URLSession? = NCNetworking.shared.sessionManagerDownloadBackground - if let tasks = await session?.tasks { - for task in tasks.2 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) - if task.taskIdentifier == metadata.sessionTaskIdentifier { - task.cancel() - } - } - } - } - - // UPLOAD FOREGROUND - if metadata.session == NextcloudKit.shared.nkCommonInstance.identifierSessionUpload { - if let request = NCNetworking.shared.uploadRequest[fileNameLocalPath] { - request.cancel() - NCNetworking.shared.uploadRequest.removeValue(forKey: fileNameLocalPath) - } - NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) - NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile), - object: nil, - userInfo: ["ocId": metadata.ocId, - "serverUrl": metadata.serverUrl, - "account": metadata.account]) - return - } - - // UPLOAD BACKGROUND - var session: URLSession? - if metadata.session == NCNetworking.shared.sessionUploadBackground { - session = NCNetworking.shared.sessionManagerUploadBackground - } else if metadata.session == NCNetworking.shared.sessionUploadBackgroundWWan { - session = NCNetworking.shared.sessionManagerUploadBackgroundWWan - } - if let tasks = await session?.tasks { - for task in tasks.1 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) - if task.taskIdentifier == metadata.sessionTaskIdentifier { - task.cancel() - NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) - NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile), - object: nil, - userInfo: ["ocId": metadata.ocId, - "serverUrl": metadata.serverUrl, - "account": metadata.account]) - } - } - } - } func removeImageCache(metadatas: [tableMetadata]) { DispatchQueue.global().async { diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift index 3f2d134ddc..d482a94e50 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift @@ -128,15 +128,16 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { } if self.isEditMode { - if let index = self.fileSelect.firstIndex(of: metadata.ocId) { - self.fileSelect.remove(at: index) - } else { - self.fileSelect.append(metadata.ocId) + if !metadata.e2eEncrypted { + if let index = self.fileSelect.firstIndex(of: metadata.ocId) { + self.fileSelect.remove(at: index) + } else { + self.fileSelect.append(metadata.ocId) + } + self.collectionView.reloadItems(at: [indexPath]) + self.tabBarSelect?.update(fileSelect: self.fileSelect, metadatas: self.getSelectedMetadatas(), userId: metadata.userId) + // self.collectionView.reloadSections(IndexSet(integer: indexPath.section)) } - self.collectionView.reloadItems(at: [indexPath]) - self.tabBarSelect?.update(fileSelect: self.fileSelect, metadatas: self.getSelectedMetadatas(), userId: metadata.userId) - // self.collectionView.reloadSections(IndexSet(integer: indexPath.section)) - self.collectionView.collectionViewLayout.invalidateLayout() return } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index cdb67aa1a4..346adb99f0 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -1,25 +1,6 @@ -// -// NCCollectionViewCommon.swift -// Nextcloud -// -// Created by Marino Faggiana on 12/09/2020. -// Copyright © 2020 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2020 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import SwiftUI @@ -30,7 +11,7 @@ import EasyTipView import LucidBanner import MoEngageInApps -class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionHeaderMenuDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate, NCEmptyDataSetDelegate { +class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionFirstHeaderDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { @IBOutlet weak var collectionView: UICollectionView! @@ -40,6 +21,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let utilityFileSystem = NCUtilityFileSystem() let imageCache = NCImageCache.shared var dataSource = NCCollectionViewDataSource() + let networking = NCNetworking.shared let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! var pinchGesture: UIPinchGestureRecognizer = UIPinchGestureRecognizer() @@ -50,7 +32,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var backgroundImageView = UIImageView() var serverUrl: String = "" var isEditMode = false - var isDirectoryEncrypted = false + var isDirectoryE2EE = false var fileSelect: [String] = [] var metadataFolder: tableMetadata? var richWorkspaceText: String? @@ -59,7 +41,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var isSearchingMode: Bool = false var networkSearchInProgress: Bool = false var layoutForView: NCDBLayoutForView? - var dataSourceTask: URLSessionTask? + var searchDataSourceTask: URLSessionTask? var providers: [NKSearchProvider]? var searchResults: [NKSearchResult]? var listLayout = NCListLayout() @@ -70,9 +52,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var tabBarSelect: NCCollectionViewCommonSelectTabBar? var attributesZoomIn: UIMenuElement.Attributes = [] var attributesZoomOut: UIMenuElement.Attributes = [] - let maxImageGrid: CGFloat = 7 -// var headerMenu: NCSectionFirstHeader? - var tipViewAccounts: EasyTipView? var syncMetadatasTask: Task? @@ -82,17 +61,17 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var headerMenuButtonsView: Bool = true var headerRichWorkspaceDisable: Bool = false + var selectableDataSource: [RealmSwiftObject] { dataSource.getMetadataSourceForAllSections() } + // DECLARE var layoutKey = "" var titleCurrentFolder = "" var titlePreviusFolder: String? var enableSearchBar: Bool = false - - var groupByField = "name" + var headerRichWorkspaceDisable: Bool = false var emptyImageName: String? var emptyImageColors: [UIColor]? - var emptyImage: UIImage? var emptyTitle: String = "" var emptyDescription: String = "" @@ -111,14 +90,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var numberOfColumns: Int = 0 var lastNumberOfColumns: Int = 0 - var isTransitioning: Bool = false - var selectableDataSource: [RealmSwiftObject] { dataSource.getMetadataSourceForAllSections() } - var pushed: Bool = false - var emptyDataSet: NCEmptyDataSet? - let heightHeaderRecommendations: CGFloat = 160 let heightHeaderSection: CGFloat = 30 + @MainActor var session: NCSession.Session { NCSession.shared.getSession(controller: tabBarController) } @@ -136,30 +111,29 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } var showDescription: Bool { - !headerRichWorkspaceDisable && NCKeychain().showDescription + !headerRichWorkspaceDisable && NCPreferences().showDescription } var isRecommendationActived: Bool { - self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session) && - NCCapabilities.shared.getCapabilities(account: self.session.account).capabilityRecommendations + let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() + return self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session) && capabilities.recommendations } var infoLabelsSeparator: String { layoutForView?.layout == global.layoutList ? " - " : "" } + @MainActor var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController } - var defaultPredicate: NSPredicate { - let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, NCGlobal.shared.metadataStatusHideInView, NKCommon.TypeClassFile.video.rawValue) - return predicate + var mainNavigationController: NCMainNavigationController? { + self.navigationController as? NCMainNavigationController } - var personalFilesOnlyPredicate: NSPredicate { - let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND (ownerId == %@ || ownerId == '') AND mountType == '' AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, session.userId, global.metadataStatusHideInView, NKCommon.TypeClassFile.video.rawValue) - return predicate + var sceneIdentifier: String { + (self.tabBarController as? NCMainTabBarController)?.sceneIdentifier ?? "" } var isNumberOfItemsInAllSectionsNull: Bool { @@ -193,18 +167,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewDidLoad() { super.viewDidLoad() - tabBarSelect = NCCollectionViewCommonSelectTabBar(controller: self.controller, delegate: self) self.navigationController?.presentationController?.delegate = self collectionView.alwaysBounceVertical = true collectionView.accessibilityIdentifier = "NCCollectionViewCommon" view.backgroundColor = .systemBackground collectionView.backgroundColor = .systemBackground - refreshControl.tintColor = .gray - - listLayout = NCListLayout() - gridLayout = NCGridLayout() - + refreshControl.tintColor = .clear + if enableSearchBar { searchController = UISearchController(searchResultsController: nil) searchController?.searchResultsUpdater = self @@ -213,8 +183,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS searchController?.searchBar.delegate = self searchController?.searchBar.autocapitalizationType = .none navigationItem.searchController = searchController - navigationItem.hidesSearchBarWhenScrolling = false - navigationItem.backBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_back_", comment: ""), style: .plain, target: nil, action: nil) + navigationItem.hidesSearchBarWhenScrolling = true } // Cell @@ -224,8 +193,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS collectionView.register(UINib(nibName: "NCTransferCell", bundle: nil), forCellWithReuseIdentifier: "transferCell") // Header - collectionView.register(UINib(nibName: "NCSectionHeaderMenu", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionHeaderMenu") + collectionView.register(UINib(nibName: "NCSectionFirstHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionFirstHeader") + collectionView.register(UINib(nibName: "NCSectionFirstHeader", bundle: nil), forSupplementaryViewOfKind: mediaSectionHeader, withReuseIdentifier: "sectionFirstHeader") collectionView.register(UINib(nibName: "NCSectionHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionHeader") + collectionView.register(UINib(nibName: "NCSectionHeader", bundle: nil), forSupplementaryViewOfKind: mediaSectionHeader, withReuseIdentifier: "sectionHeader") + collectionView.register(UINib(nibName: "NCSectionFirstHeaderEmptyData", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionFirstHeaderEmptyData") + collectionView.register(UINib(nibName: "NCSectionFirstHeaderEmptyData", bundle: nil), forSupplementaryViewOfKind: mediaSectionHeader, withReuseIdentifier: "sectionFirstHeaderEmptyData") // Footer collectionView.register(UINib(nibName: "NCSectionFooter", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "sectionFooter") @@ -233,21 +206,18 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS collectionView.refreshControl = refreshControl refreshControl.action(for: .valueChanged) { _ in - self.dataSource.removeAll() - self.getServerData() - if self.isRecommendationActived { - Task.detached { - await NCNetworking.shared.createRecommendations(session: self.session) - } - } - self.refreshControl.endRefreshing() - DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { - self.resetPlusButtonAlpha() + Task { @MainActor in + // Perform async server forced + await self.getServerData(forced: true) + + // Stop the refresh control after data is loaded + self.refreshControl.endRefreshing() + + // Wait 1.5 seconds before resetting the button alpha + try? await Task.sleep(nanoseconds: 1_500_000_000) + self.mainNavigationController?.resetPlusButtonAlpha() } } - - // Empty - emptyDataSet = NCEmptyDataSet(view: collectionView, offset: getHeaderHeight(), delegate: self) let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPressCollecationView(_:))) longPressedGesture.minimumPressDuration = 0.5 @@ -265,7 +235,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let dropInteraction = UIDropInteraction(delegate: self) self.navigationController?.navigationItem.leftBarButtonItems?.first?.customView?.addInteraction(dropInteraction) - + if(!UserDefaults.standard.bool(forKey: "isInitialPrivacySettingsShowed") || isApplicationUpdated()){ redirectToPrivacyViewController() @@ -274,10 +244,17 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS UserDefaults.standard.set(appVersion, forKey: "CurrentAppVersion") } - NotificationCenter.default.addObserver(self, selector: #selector(changeTheming(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeTheming), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(reloadDataSource(_:)), name: NSNotification.Name(rawValue: global.notificationCenterReloadDataSource), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(getServerData(_:)), name: NSNotification.Name(rawValue: global.notificationCenterGetServerData), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(reloadHeader(_:)), name: NSNotification.Name(rawValue: global.notificationCenterReloadHeader), object: nil) + registerForTraitChanges([UITraitUserInterfaceStyle.self]) { [weak self] (view: NCCollectionViewCommon, _) in + guard let self else { return } + + self.sectionFirstHeader?.setRichWorkspaceColor(style: view.traitCollection.userInterfaceStyle) + } + + NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterChangeTheming), object: nil, queue: .main) { [weak self] _ in + guard let self else { return } + self.collectionView.reloadData() + } + NotificationCenter.default.addObserver(self, selector: #selector(updateIcons), name: NSNotification.Name(rawValue: global.notificationCenterUpdateIcons), object: nil) DispatchQueue.main.async { @@ -288,31 +265,23 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - AnalyticsHelper.shared.displayInAppNotification() - if titlePreviusFolder != nil { navigationController?.navigationBar.topItem?.title = titlePreviusFolder } navigationItem.title = titleCurrentFolder - navigationController?.setNavigationBarAppearance() - navigationController?.navigationBar.prefersLargeTitles = true - navigationController?.setNavigationBarHidden(false, animated: true) - - appDelegate.activeViewController = self - appDelegate.account = session.account - appDelegate.urlBase = session.urlBase - appDelegate.userId = session.userId - appDelegate.user = session.user - NCKeychain().setAccountName(account: session.account) + if tabBarSelect == nil { + tabBarSelect = NCCollectionViewCommonSelectTabBar(controller: self.controller, viewController: self, delegate: self) + } + isEditMode = false - /// Magentacloud branding changes hide user account button on left navigation bar -// setNavigationLeftItems() - setNavigationRightItems() + Task { + await (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() + await (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() + } layoutForView = database.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) - gridLayout.column = CGFloat(layoutForView?.columnGrid ?? 3) if isLayoutList { collectionView?.collectionViewLayout = listLayout self.layoutType = global.layoutList @@ -333,97 +302,37 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - NCNetworking.shared.transferDelegate = self + Task { + await NCNetworking.shared.transferDispatcher.addDelegate(self) + } NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(closeRichWorkspaceWebView), name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(changeStatusFolderE2EE(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeStatusFolderE2EE), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(changeLayout(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeLayout), object: nil) - - NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDeleteFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(copyMoveFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterCopyMoveFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(renameFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterRenameFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(createFolder(_:)), name: NSNotification.Name(rawValue: global.notificationCenterCreateFolder), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(favoriteFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterFavoriteFile), object: nil) - - NotificationCenter.default.addObserver(self, selector: #selector(downloadStartFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDownloadStartFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(downloadedFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDownloadedFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(downloadCancelFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDownloadCancelFile), object: nil) - - NotificationCenter.default.addObserver(self, selector: #selector(uploadStartFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadStartFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadedFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(uploadedLivePhoto(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadedLivePhoto), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(uploadCancelFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadCancelFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(updateShare(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUpdateShare), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(triggerProgressTask(_:)), name: NSNotification.Name(rawValue: global.notificationCenterProgressTask), object: nil) - - // FIXME: iPAD PDF landscape mode iOS 16 - DispatchQueue.main.async { - self.collectionView?.collectionViewLayout.invalidateLayout() - } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - NCNetworking.shared.cancelUnifiedSearchFiles() + self.networking.cancelUnifiedSearchFiles() dismissTip() - pushed = false - toggleSelect(isOn: false) + // Cancel Queue & Retrieves Properties - NCNetworking.shared.downloadThumbnailQueue.cancelAll() - NCNetworking.shared.unifiedSearchQueue.cancelAll() - dataSourceTask?.cancel() + self.networking.downloadThumbnailQueue.cancelAll() + self.networking.unifiedSearchQueue.cancelAll() + searchDataSourceTask?.cancel() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - NCNetworking.shared.transferDelegate = nil + Task { + await NCNetworking.shared.transferDispatcher.removeDelegate(self) + } NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterChangeStatusFolderE2EE), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterChangeLayout), object: nil) - - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDeleteFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCopyMoveFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCopyFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterMoveFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterRenameFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCreateFolder), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterFavoriteFile), object: nil) - - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterMoveFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCreateFolder), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterFavoriteFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadStartFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadedFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadCancelFile), object: nil) - - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadStartFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadedFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadedLivePhoto), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadCancelFile), object: nil) - - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUpdateShare), object: nil) - - dataSource.removeImageCache() - } - - func isApplicationUpdated() -> Bool { - let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String ?? "" - let currentVersion = UserDefaults.standard.string(forKey: "CurrentAppVersion") - return currentVersion != appVersion - } - func redirectToPrivacyViewController() { - let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) - let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as! UINavigationController - newViewController.modalPresentationStyle = .fullScreen - self.present(newViewController, animated: true, completion: nil) + removeImageCache(metadatas: self.dataSource.getMetadatas()) } func presentationControllerDidDismiss( _ presentationController: UIPresentationController) { @@ -450,9 +359,23 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override var canBecomeFirstResponder: Bool { return true } + + @objc func updateIcons() { + collectionView.reloadData() + } - override func viewWillLayoutSubviews() { - super.viewWillLayoutSubviews() + func isApplicationUpdated() -> Bool { + let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String ?? "" + let currentVersion = UserDefaults.standard.string(forKey: "CurrentAppVersion") + return currentVersion != appVersion + } + + func redirectToPrivacyViewController() { + let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) + let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as? UINavigationController + newViewController?.modalPresentationStyle = .fullScreen + self.present(newViewController!, animated: true, completion: nil) + } tabBarSelect?.setFrame() } @@ -564,412 +487,80 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - NotificationCenter @objc func applicationWillResignActive(_ notification: NSNotification) { - self.resetPlusButtonAlpha() - self.refreshControl.endRefreshing() + mainNavigationController?.resetPlusButtonAlpha() } - @objc func reloadAvatar(_ notification: NSNotification) { - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - self.showTipAccounts() + @objc func closeRichWorkspaceWebView() { + Task { + await self.reloadDataSource() } - guard let userInfo = notification.userInfo as NSDictionary?, - let error = userInfo["error"] as? NKError, - error.errorCode != global.errorNotModified else { return } - /// Magentacloud branding changes hide user account button on left navigation bar - setNavigationLeftItems() - } - - @objc func changeTheming(_ notification: NSNotification) { - self.reloadDataSource() } - @objc func changeLayout(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let account = userInfo["account"] as? String, - let serverUrl = userInfo["serverUrl"] as? String, - let layoutForView = userInfo["layoutForView"] as? NCDBLayoutForView, - account == session.account, - serverUrl == self.serverUrl - else { return } - - if self.layoutForView?.layout == layoutForView.layout { - self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) - self.reloadDataSource() - return - } - - self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) - layoutForView.layout = layoutForView.layout - self.layoutType = layoutForView.layout - - collectionView.reloadData() - - switch layoutForView.layout { - case global.layoutList: - self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) - case global.layoutGrid: - self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) - case global.layoutPhotoSquare, global.layoutPhotoRatio: - self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) - default: - break - } - - self.collectionView.collectionViewLayout.invalidateLayout() + // MARK: - Layout -// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() - } + func changeLayout(layoutForView: NCDBLayoutForView) { + let homeServer = utilityFileSystem.getHomeServer(urlBase: session.urlBase, userId: session.userId) + let numFoldersLayoutsForView = self.database.getLayoutsForView(keyStore: layoutForView.keyStore)?.count ?? 1 - @objc func reloadDataSource(_ notification: NSNotification) { - if let userInfo = notification.userInfo as? NSDictionary { - if let serverUrl = userInfo["serverUrl"] as? String { - if serverUrl != self.serverUrl { - return + func changeLayout(withSubFolders: Bool) { + if self.layoutForView?.layout == layoutForView.layout { + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) + Task { + await self.reloadDataSource() } - } - - if let clearDataSource = userInfo["clearDataSource"] as? Bool, clearDataSource { - self.dataSource.removeAll() - } - } - - reloadDataSource() - } - - @objc func getServerData(_ notification: NSNotification) { - if let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String { - if serverUrl != self.serverUrl { return } - } - - getServerData() - } - - @objc func reloadHeader(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let account = userInfo["account"] as? String, - account == session.account - else { return } - - self.collectionView.reloadData() - } - - @objc func changeStatusFolderE2EE(_ notification: NSNotification) { - reloadDataSource() - } - - @objc func closeRichWorkspaceWebView() { - reloadDataSource() - } - - @objc func deleteFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let error = userInfo["error"] as? NKError else { return } - - if error == .success { - if isSearchingMode { - return networkSearch() - } - - if isRecommendationActived { - Task.detached { - await NCNetworking.shared.createRecommendations(session: self.session) - } - } - } else { - NCContentPresenter().showError(error: error) - } - - reloadDataSource() - } - - @objc func copyMoveFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String, - account == session.account else { return } - - if isSearchingMode { - return networkSearch() - } - - if isRecommendationActived { - Task.detached { - await NCNetworking.shared.createRecommendations(session: self.session) - } - } - - if serverUrl == self.serverUrl { - reloadDataSource() - } - } - - @objc func renameFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let account = userInfo["account"] as? String, - let serverUrl = userInfo["serverUrl"] as? String, - let error = userInfo["error"] as? NKError, - account == session.account - else { return } - - if error == .success { - if isSearchingMode { - return networkSearch() - } - if isRecommendationActived { - Task.detached { - await NCNetworking.shared.createRecommendations(session: self.session) - } - } - } + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) + layoutForView.layout = layoutForView.layout + self.layoutType = layoutForView.layout - if serverUrl == self.serverUrl { - if error != .success { - NCContentPresenter().showError(error: error) - } - reloadDataSource() - } else { collectionView.reloadData() - } - } - - @objc func createFolder(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let ocId = userInfo["ocId"] as? String, - let account = userInfo["account"] as? String, - account == session.account, - let withPush = userInfo["withPush"] as? Bool, - let metadata = database.getMetadataFromOcId(ocId) - else { return } - - if isSearchingMode { - return networkSearch() - } - if metadata.serverUrl + "/" + metadata.fileName == self.serverUrl { - reloadDataSource() - } else if withPush, metadata.serverUrl == self.serverUrl { - reloadDataSource() - if let sceneIdentifier = userInfo["sceneIdentifier"] as? String { - if sceneIdentifier == controller?.sceneIdentifier { - pushMetadata(metadata) - } - } else { - pushMetadata(metadata) + switch layoutForView.layout { + case global.layoutList: + self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) + case global.layoutGrid: + self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) + case global.layoutPhotoSquare, global.layoutPhotoRatio: + self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) + default: + break } - } - } - - @objc func favoriteFile(_ notification: NSNotification) { - if isSearchingMode { - return networkSearch() - } - - if self is NCFavorite { - return reloadDataSource() - } - - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - serverUrl == self.serverUrl - else { return } - reloadDataSource() - } - - @objc func downloadStartFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func downloadedFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func downloadCancelFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } + self.collectionView.collectionViewLayout.invalidateLayout() - @objc func uploadStartFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let ocId = userInfo["ocId"] as? String, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String, - !isSearchingMode, - let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) - else { return } - - // Header view trasfer - if metadata.isTransferInForeground { - NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: 0) - DispatchQueue.main.async { self.collectionView?.reloadData() } - } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func uploadedFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() + Task { + await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + } } - } - - @objc func uploadedLivePhoto(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() + if serverUrl == homeServer || numFoldersLayoutsForView == 1 { + changeLayout(withSubFolders: false) } else { - collectionView?.reloadData() - } - } + let alertController = UIAlertController(title: NSLocalizedString("_propagate_layout_", comment: ""), message: nil, preferredStyle: .alert) - @objc func uploadCancelFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func triggerProgressTask(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let progressNumber = userInfo["progress"] as? NSNumber, - let totalBytes = userInfo["totalBytes"] as? Int64, - let totalBytesExpected = userInfo["totalBytesExpected"] as? Int64, - let ocId = userInfo["ocId"] as? String, - let ocIdTransfer = userInfo["ocIdTransfer"] as? String, - let session = userInfo["session"] as? String - else { return } - - let chunk: Int = userInfo["chunk"] as? Int ?? 0 - let e2eEncrypted: Bool = userInfo["e2eEncrypted"] as? Bool ?? false - - let transfer = NCTransferProgress.shared.append(NCTransferProgress.Transfer(ocId: ocId, ocIdTransfer: ocIdTransfer, session: session, chunk: chunk, e2eEncrypted: e2eEncrypted, progressNumber: progressNumber, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected)) - -// DispatchQueue.main.async { - if self.headerMenuTransferView && (chunk > 0 || e2eEncrypted) { - if NCNetworking.shared.transferInForegorund?.ocId == ocId { - NCNetworking.shared.transferInForegorund?.progress = progressNumber.floatValue - } else { - NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: progressNumber.floatValue) - self.collectionView.reloadData() - } - self.headerMenu?.progressTransfer.progress = transfer.progressNumber.floatValue - } else { - guard let indexPath = self.dataSource.getIndexPathMetadata(ocId: ocId), - let cell = self.collectionView?.cellForItem(at: indexPath), - let cell = cell as? NCCellProtocol else { return } - if progressNumber.floatValue == 1 && !(cell is NCTransferCell) { - cell.fileProgressView?.isHidden = true - cell.fileProgressView?.progress = .zero - cell.setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCImageCache.images.buttonMore) - if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) { - cell.writeInfoDateSize(date: metadata.date, size: metadata.size) - } else { - cell.fileInfoLabel?.text = "" - cell.fileSubinfoLabel?.text = "" - } - } else { - cell.fileProgressView?.isHidden = false - cell.fileProgressView?.progress = progressNumber.floatValue - cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCImageCache.images.buttonStop) - let status = userInfo["status"] as? Int ?? NCGlobal.shared.metadataStatusNormal - if status == NCGlobal.shared.metadataStatusDownloading { - cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) - cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↓ " + self.utilityFileSystem.transformedSize(totalBytes) - } else if status == NCGlobal.shared.metadataStatusUploading { - if totalBytes > 0 { - cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) - cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ " + self.utilityFileSystem.transformedSize(totalBytes) - } else { - cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) - cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ …" - } - } - } - } -// } - } + alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default, handler: { _ in + changeLayout(withSubFolders: true) + })) + alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", comment: ""), style: .default, handler: { _ in + changeLayout(withSubFolders: false) + })) - @objc func updateShare(_ notification: NSNotification) { - if isSearchingMode { - networkSearch() - } else { -// self.dataSource.removeAll() - getServerData() + self.present(alertController, animated: true) } } - @objc func updateIcons() { - collectionView.reloadData() -// reloadDataSource() - } - - // MARK: - Layout - - func setNavigationLeftItems() { - navigationItem.title = titleCurrentFolder - } - func getNavigationTitle() -> String { - let tableAccount = self.database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) - if let tableAccount, - !tableAccount.alias.isEmpty { - return tableAccount.alias + let tblAccount = self.database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) + if let tblAccount, + !tblAccount.alias.isEmpty { + return tblAccount.alias } return NCBrandOptions.shared.brand } - func accountSettingsDidDismiss(tableAccount: tableAccount?, controller: NCMainTabBarController?) { } + func accountSettingsDidDismiss(tblAccount: tableAccount?, controller: NCMainTabBarController?) { } func resetPlusButtonAlpha(animated: Bool = true) { } @@ -1037,8 +628,29 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS view.emptyDescription.text = NSLocalizedString("_no_file_pull_down_", comment: "") } } + + let spinner = UIActivityIndicatorView(style: .medium) + spinner.startAnimating() + + let container = UIView() + container.translatesAutoresizingMaskIntoConstraints = false + container.addSubview(spinner) + + spinner.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + spinner.centerXAnchor.constraint(equalTo: container.centerXAnchor), + spinner.centerYAnchor.constraint(equalTo: container.centerYAnchor) + ]) + + self.navigationItem.titleView = container } - + + @MainActor + func restoreDefaultTitle() { + self.navigationItem.titleView = nil + self.navigationItem.title = self.titleCurrentFolder + } + // MARK: - SEARCH func searchController(enabled: Bool) { @@ -1048,6 +660,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS searchController?.searchBar.alpha = 1 } else { searchController?.searchBar.alpha = 0.3 + } } @@ -1059,9 +672,13 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS isSearchingMode = true self.providers?.removeAll() self.dataSource.removeAll() - self.reloadDataSource() + Task { + await self.reloadDataSource() + } // TIP dismissTip() + // + mainNavigationController?.hiddenPlusButton(true) } func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { @@ -1071,30 +688,30 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { - NCNetworking.shared.cancelUnifiedSearchFiles() + self.networking.cancelUnifiedSearchFiles() + self.isSearchingMode = false self.literalSearch = "" self.providers?.removeAll() self.dataSource.removeAll() - self.reloadDataSource() + Task { + await self.reloadDataSource() + } + // + mainNavigationController?.hiddenPlusButton(false) } // MARK: - TAP EVENT - func tapMoreListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { - tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: image, sender: sender) - } - func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) } - func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { - tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: image, sender: sender) + func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) } func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) { - if isEditMode { return } guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) @@ -1103,29 +720,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS appDelegate.adjust.trackEvent(TriggerEvent(Sharing.rawValue)) } - func tapMoreGridItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { - if isEditMode { return } - guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } -// toggleMenu(metadata: metadata, image: image) - if namedButtonMore == NCGlobal.shared.buttonMoreMore || namedButtonMore == NCGlobal.shared.buttonMoreLock { - toggleMenu(metadata: metadata, image: image) - } else if namedButtonMore == NCGlobal.shared.buttonMoreStop { - Task { - await cancelSession(metadata: metadata) - } - } - } - func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } -// toggleMenu(metadata: metadata, image: image) - Task { - await cancelSession(metadata: metadata) - } + toggleMenu(metadata: metadata, image: image, sender: sender) } - func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func tapRichWorkspace(_ sender: Any) { if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { if let viewerRichWorkspace = navigationController.topViewController as? NCViewerRichWorkspace { @@ -1139,8 +738,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } - func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?) { - toggleMenu(metadata: metadata, image: image) + func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { + toggleMenu(metadata: metadata, image: image, sender: sender) } func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { @@ -1151,46 +750,16 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS didSelectMetadata(metadata, withOcIds: false) } - func tapButtonSwitch(_ sender: Any) { - guard !isTransitioning else { return } - isTransitioning = true - - guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) else { return } - - if layoutForView.layout == NCGlobal.shared.layoutGrid { - layoutForView.layout = NCGlobal.shared.layoutList - } else { - layoutForView.layout = NCGlobal.shared.layoutGrid - } - self.layoutForView = NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView) - self.collectionView.reloadData() - self.collectionView.collectionViewLayout.invalidateLayout() - self.collectionView.setCollectionViewLayout(layoutForView.layout == NCGlobal.shared.layoutList ? self.listLayout : self.gridLayout, animated: true) {_ in self.isTransitioning = false } - } - - func tapButtonOrder(_ sender: Any) { - let sortMenu = NCSortMenu() - sortMenu.toggleMenu(viewController: self, account: session.account, key: layoutKey, sortButton: sender as? UIButton, serverUrl: serverUrl) - } - - func tapButtonTransfer(_ sender: Any) { } - - func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { } - func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func longPressListItem(with objectId: String, indexPath: IndexPath, gestureRecognizer: UILongPressGestureRecognizer) { - } - func longPressListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func longPressMoreListItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - - func longPressGridItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } - - func longPressMoreGridItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } - + + func longPressMoreGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) } @@ -1212,7 +781,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS becomeFirstResponder() if !serverUrl.isEmpty { - listMenuItems.append(UIMenuItem(title: NSLocalizedString("_paste_file_", comment: ""), action: #selector(pasteFilesMenu))) + listMenuItems.append(UIMenuItem(title: NSLocalizedString("_paste_file_", comment: ""), action: #selector(pasteFilesMenu(_:)))) } if !listMenuItems.isEmpty { @@ -1221,23 +790,16 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } - // MARK: - Transfer Delegate - - func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } - - func tranferChange(status: String, metadata: tableMetadata, error: NKError) { } - // MARK: - Menu Item override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { - - if #selector(pasteFilesMenu) == action { + if #selector(pasteFilesMenu(_:)) == action { if !UIPasteboard.general.items.isEmpty, !(metadataFolder?.e2eEncrypted ?? false) { return true } - } else if #selector(copyMenuFile) == action { + } else if #selector(copyMenuFile(_:)) == action { return true - } else if #selector(moveMenuFile) == action { + } else if #selector(moveMenuFile(_:)) == action { return true } @@ -1321,43 +883,27 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - DataSource - @objc func reloadDataSource() { - - // get auto upload folder - autoUploadFileName = NCManageDatabase.shared.getAccountAutoUploadFileName() - autoUploadDirectory = NCManageDatabase.shared.getAccountAutoUploadDirectory(urlBase: session.urlBase, userId: session.userId, account: session.account) - - // get layout for view - layoutForView = NCManageDatabase.shared.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) - // set GroupField for Grid - if !isSearchingMode && layoutForView?.layout == NCGlobal.shared.layoutGrid { - groupByField = "classFile" - } else { - groupByField = "name" - } - if isSearchingMode { - isDirectoryEncrypted = false - } else { - isDirectoryEncrypted = NCUtilityFileSystem().isDirectoryE2EE(session: session, serverUrl: serverUrl) + @MainActor + func reloadDataSource() async { + if !isSearchingMode { + Task.detached { + if await self.isRecommendationActived() { + await self.networking.createRecommendations(session: self.session, serverUrl: self.serverUrl, collectionView: self.collectionView) + } + } } - DispatchQueue.main.async { -// UIView.transition(with: self.collectionView, -// duration: 0.20, -// options: .transitionCrossDissolve, -// animations: { self.collectionView.reloadData() }, -// completion: nil) -// -// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() - self.refreshControl.endRefreshing() - self.collectionView.reloadData() - self.setNavigationRightItems() - } - } + UIView.transition(with: self.collectionView, + duration: 0.20, + options: .transitionCrossDissolve, + animations: { self.collectionView.reloadData() }, + completion: nil) - func getServerData() { + await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() } + func getServerData(forced: Bool = false) async { } + @objc func networkSearch() { guard !networkSearchInProgress else { return @@ -1365,45 +911,68 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS guard !session.account.isEmpty, let literalSearch = literalSearch, !literalSearch.isEmpty else { - return self.refreshControl.endRefreshing() + return } + let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() self.networkSearchInProgress = true self.dataSource.removeAll() - self.refreshControl.beginRefreshing() - self.reloadDataSource() - - if NCCapabilities.shared.getCapabilities(account: session.account).capabilityServerVersionMajor >= global.nextcloudVersion20 { - NCNetworking.shared.unifiedSearchFiles(literal: literalSearch, account: session.account) { task in - self.dataSourceTask = task - self.reloadDataSource() - } providers: { _, searchProviders in + Task { + await self.reloadDataSource() + } + + if capabilities.serverVersionMajor >= global.nextcloudVersion20 { + self.networking.unifiedSearchFiles(literal: literalSearch, account: session.account) { task in + self.searchDataSourceTask = task + Task { + await self.reloadDataSource() + } + } providers: { account, searchProviders in self.providers = searchProviders self.searchResults = [] - self.dataSource = NCCollectionViewDataSource(metadatas: [], layoutForView: self.layoutForView, providers: self.providers, searchResults: self.searchResults) + self.dataSource = NCCollectionViewDataSource(metadatas: [], + layoutForView: self.layoutForView, + providers: self.providers, + searchResults: self.searchResults, + account: account) } update: { _, _, searchResult, metadatas in guard let metadatas, !metadatas.isEmpty, self.isSearchingMode, let searchResult else { return } - NCNetworking.shared.unifiedSearchQueue.addOperation(NCCollectionViewUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult)) + self.networking.unifiedSearchQueue.addOperation(NCCollectionViewUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult)) } completion: { _, _ in - self.refreshControl.endRefreshing() - self.reloadDataSource() + Task { + await self.reloadDataSource() + } self.networkSearchInProgress = false } } else { - NCNetworking.shared.searchFiles(literal: literalSearch, account: session.account) { task in - self.dataSourceTask = task - self.reloadDataSource() + self.networking.searchFiles(literal: literalSearch, account: session.account) { task in + self.searchDataSourceTask = task + Task { + await self.reloadDataSource() + } } completion: { metadatasSearch, error in - DispatchQueue.main.async { - self.refreshControl.endRefreshing() - self.reloadDataSource() + Task { + guard let metadatasSearch, + error == .success, + self.isSearchingMode + else { + self.networkSearchInProgress = false + await self.reloadDataSource() + return + } + let ocId = metadatasSearch.map { $0.ocId } + let metadatas = await self.database.getMetadatasAsync(predicate: NSPredicate(format: "ocId IN %@", ocId), + withLayout: self.layoutForView, + withAccount: self.session.account) + + self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, + layoutForView: self.layoutForView, + providers: self.providers, + searchResults: self.searchResults, + account: self.session.account) + self.networkSearchInProgress = false + await self.reloadDataSource() } - guard let metadatasSearch, error == .success, self.isSearchingMode else { return } - let ocId = metadatasSearch.map { $0.ocId } - let metadatas = self.database.getResultsMetadatasPredicate(NSPredicate(format: "ocId IN %@", ocId), layoutForView: self.layoutForView) - - self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, layoutForView: self.layoutForView, providers: self.providers, searchResults: self.searchResults) - self.networkSearchInProgress = false } } } @@ -1414,9 +983,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS metadataForSection.unifiedSearchInProgress = true self.collectionView?.reloadData() - NCNetworking.shared.unifiedSearchFilesProvider(id: lastSearchResult.id, term: term, limit: 5, cursor: cursor, account: session.account) { task in - self.dataSourceTask = task - self.reloadDataSource() + self.networking.unifiedSearchFilesProvider(id: lastSearchResult.id, term: term, limit: 5, cursor: cursor, account: session.account) { task in + self.searchDataSourceTask = task + Task { + await self.reloadDataSource() + } } completion: { _, searchResult, metadatas, error in if error != .success { Task {@MainActor in @@ -1441,8 +1012,15 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - Push metadata func pushMetadata(_ metadata: tableMetadata) { - guard let navigationCollectionViewCommon = self.controller?.navigationCollectionViewCommon else { return } - let serverUrlPush = utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName) + guard let navigationCollectionViewCommon = self.controller?.navigationCollectionViewCommon else { + return + } + let serverUrlPush = utilityFileSystem.createServerUrl(serverUrl: metadata.serverUrl, fileName: metadata.fileName) + + // Set Last Opening Date + Task { + await database.setDirectoryLastOpeningDateAsync(ocId: metadata.ocId) + } if let viewController = navigationCollectionViewCommon.first(where: { $0.navigationController == self.navigationController && $0.serverUrl == serverUrlPush})?.viewController, viewController.isViewLoaded { navigationController?.pushViewController(viewController, animated: true) @@ -1458,83 +1036,40 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } } - - func pushViewController(viewController: UIViewController) { - if pushed { return } - - pushed = true - navigationController?.pushViewController(viewController, animated: true) - } // MARK: - Header size -// func getHeaderHeight(section: Int) -> (heightHeaderCommands: CGFloat, heightHeaderRichWorkspace: CGFloat, heightHeaderSection: CGFloat) { -// var headerRichWorkspace: CGFloat = 0 -// -// func getHeaderHeight() -> CGFloat { -// var size: CGFloat = 0 -// -// if isHeaderMenuTransferViewEnabled() != nil { -// if !isSearchingMode { -// size += global.heightHeaderTransfer -// } -// } -// if headerMenuButtonsView { -// size += NCGlobal.shared.heightButtonsView -// } -// return size -// } -// -//// func getHeaderHeight(section: Int) -> (heightHeaderRichWorkspace: CGFloat, -//// heightHeaderRecommendations: CGFloat, -//// heightHeaderSection: CGFloat) { -//// var heightHeaderRichWorkspace: CGFloat = 0 -//// var heightHeaderRecommendations: CGFloat = 0 -//// var heightHeaderSection: CGFloat = 0 -//// -//// if showDescription, -//// !isSearchingMode, -//// let richWorkspaceText = self.richWorkspaceText, -//// !richWorkspaceText.trimmingCharacters(in: .whitespaces).isEmpty { -//// heightHeaderRichWorkspace = UIScreen.main.bounds.size.height / 6 -//// } -//// -//// if isRecommendationActived, -//// !isSearchingMode, -//// NCKeychain().showRecommendedFiles, -//// !self.database.getRecommendedFiles(account: self.session.account).isEmpty { -//// heightHeaderRecommendations = self.heightHeaderRecommendations -//// heightHeaderSection = self.heightHeaderSection -//// } -// -// if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { -// if section == 0 { -// return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) -// } else { -// return (0, 0, self.heightHeaderSection) -// } -// } else { -// return (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) -//// -//// if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { -//// if section == 0 { -//// return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) -//// } else { -//// return (0, 0, self.heightHeaderSection) -//// } -//// } else { -//// return (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) -// } -// } - - func isHeaderMenuTransferViewEnabled() -> [tableMetadata]? { - if headerMenuTransferView, - NCNetworking.shared.isOnline, - let results = database.getResultsMetadatas(predicate: NSPredicate(format: "status IN %@", [global.metadataStatusWaitUpload, global.metadataStatusUploading])), - !results.isEmpty { - return Array(results) + func getHeaderHeight(section: Int) -> (heightHeaderRichWorkspace: CGFloat, + heightHeaderRecommendations: CGFloat, + heightHeaderSection: CGFloat) { + var heightHeaderRichWorkspace: CGFloat = 0 + var heightHeaderRecommendations: CGFloat = 0 + var heightHeaderSection: CGFloat = 0 + + if showDescription, + !isSearchingMode, + let richWorkspaceText = self.richWorkspaceText, + !richWorkspaceText.trimmingCharacters(in: .whitespaces).isEmpty { + heightHeaderRichWorkspace = UIScreen.main.bounds.size.height / 6 + } + + if isRecommendationActived, + !isSearchingMode, + NCPreferences().showRecommendedFiles, + !self.database.getRecommendedFiles(account: self.session.account).isEmpty { + heightHeaderRecommendations = self.heightHeaderRecommendations + heightHeaderSection = self.heightHeaderSection + } + + if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { + if section == 0 { + return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) + } else { + return (0, 0, self.heightHeaderSection) + } + } else { + return (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) } - return nil } func sizeForHeaderInSection(section: Int) -> CGSize { @@ -1543,12 +1078,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let isIphone = UIDevice.current.userInterfaceIdiom == .phone if self.dataSource.isEmpty() { - height = utility.getHeightHeaderEmptyData(view: view, portraitOffset: emptyDataPortaitOffset, landscapeOffset: emptyDataLandscapeOffset, isHeaderMenuTransferViewEnabled: isHeaderMenuTransferViewEnabled() != nil) + height = utility.getHeightHeaderEmptyData(view: view, portraitOffset: emptyDataPortaitOffset, landscapeOffset: emptyDataLandscapeOffset) } else if isEditMode || (isLandscape && isIphone) { return CGSize.zero } else { - let (heightHeaderCommands, heightHeaderRichWorkspace, heightHeaderSection) = getHeaderHeight(section: section) - height = heightHeaderCommands + heightHeaderRichWorkspace + heightHeaderSection + let (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) = getHeaderHeight(section: section) + height = heightHeaderRichWorkspace + heightHeaderRecommendations + heightHeaderSection } return CGSize(width: collectionView.frame.width, height: height) @@ -1557,21 +1092,25 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - Footer size func sizeForFooterInSection(section: Int) -> CGSize { + guard let controller else { + return CGSize.zero + } let sections = dataSource.numberOfSections() - let metadataForSection = self.dataSource.getMetadataForSection(section) - let isPaginated = metadataForSection?.lastSearchResult?.isPaginated ?? false - let metadatasCount: Int = metadataForSection?.lastSearchResult?.entries.count ?? 0 - var size = CGSize(width: collectionView.frame.width, height: 0) + let bottomAreaInsets: CGFloat = controller.tabBar.safeAreaInsets.bottom == 0 ? 34 : 0 + let height = controller.tabBar.frame.height + bottomAreaInsets - if section == sections - 1 { - size.height += 85 - } else { - size.height += 1 + if isEditMode { + return CGSize(width: collectionView.frame.width, height: 90 + height) } - if isSearchingMode && isPaginated && metadatasCount > 0 { - size.height += 30 + if isSearchingMode { + return CGSize(width: collectionView.frame.width, height: 50) + } + + if section == sections - 1 { + return CGSize(width: collectionView.frame.width, height: height) + } else { + return CGSize(width: collectionView.frame.width, height: 0) } - return size } } diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index 83745fc15d..3a3f982288 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -1,83 +1,40 @@ -// -// NCSectionFirstHeader.swift -// Nextcloud -// -// Created by Marino Faggiana on 09/10/2018. -// Copyright © 2018 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2018 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import MarkdownKit import NextcloudKit protocol NCSectionFirstHeaderDelegate: AnyObject { - func tapButtonSwitch(_ sender: Any) - func tapButtonOrder(_ sender: Any) - func tapButtonMore(_ sender: Any) - func tapButtonTransfer(_ sender: Any) func tapRichWorkspace(_ sender: Any) func tapRecommendations(with metadata: tableMetadata) - func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?) -} - -extension NCSectionFirstHeaderDelegate { - func tapButtonSwitch(_ sender: Any) {} - func tapButtonOrder(_ sender: Any) {} - func tapButtonMore(_ sender: Any) {} + func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) } class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegate { - - @IBOutlet weak var buttonSwitch: UIButton! - @IBOutlet weak var buttonOrder: UIButton! - @IBOutlet weak var buttonMore: UIButton! - @IBOutlet weak var buttonTransfer: UIButton! - @IBOutlet weak var imageButtonTransfer: UIImageView! - @IBOutlet weak var labelTransfer: UILabel! - @IBOutlet weak var progressTransfer: UIProgressView! - @IBOutlet weak var transferSeparatorBottom: UIView! - @IBOutlet weak var textViewRichWorkspace: UITextView! - @IBOutlet weak var labelSection: UILabel! - @IBOutlet weak var viewTransfer: UIView! @IBOutlet weak var viewRichWorkspace: UIView! @IBOutlet weak var viewRecommendations: UIView! @IBOutlet weak var viewSection: UIView! - @IBOutlet weak var viewButtonsView: UIView! - @IBOutlet weak var viewSeparator: UIView! - @IBOutlet weak var viewTransferHeightConstraint: NSLayoutConstraint! - @IBOutlet weak var viewButtonsViewHeightConstraint: NSLayoutConstraint! - @IBOutlet weak var viewSeparatorHeightConstraint: NSLayoutConstraint! @IBOutlet weak var viewRichWorkspaceHeightConstraint: NSLayoutConstraint! @IBOutlet weak var viewRecommendationsHeightConstraint: NSLayoutConstraint! @IBOutlet weak var viewSectionHeightConstraint: NSLayoutConstraint! - @IBOutlet weak var transferSeparatorBottomHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var textViewRichWorkspace: UITextView! @IBOutlet weak var collectionViewRecommendations: UICollectionView! @IBOutlet weak var labelRecommendations: UILabel! + @IBOutlet weak var labelSection: UILabel! private weak var delegate: NCSectionFirstHeaderDelegate? private let utility = NCUtility() private var markdownParser = MarkdownParser() + private let global = NCGlobal.shared private var richWorkspaceText: String? private let richWorkspaceGradient: CAGradientLayer = CAGradientLayer() private var recommendations: [tableRecommendedFiles] = [] private var viewController: UIViewController? + private var sceneIdentifier: String = "" override func awakeFromNib() { super.awakeFromNib() @@ -88,26 +45,11 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat richWorkspaceGradient.startPoint = CGPoint(x: 0, y: 0.8) richWorkspaceGradient.endPoint = CGPoint(x: 0, y: 0.9) viewRichWorkspace.layer.addSublayer(richWorkspaceGradient) - backgroundColor = .clear - - //Button - buttonSwitch.setImage(UIImage(systemName: "list.bullet")!.image(color: NCBrandColor.shared.iconColor, size: 25), for: .normal) - - buttonOrder.setTitle("", for: .normal) - buttonOrder.setTitleColor(NCBrandColor.shared.brand, for: .normal) - buttonMore.setImage(UIImage(named: "more")!.image(color: NCBrandColor.shared.iconColor, size: 25), for: .normal) - - // Gradient -// gradient.startPoint = CGPoint(x: 0, y: 0.8) -// gradient.endPoint = CGPoint(x: 0, y: 0.9) -// viewRichWorkspace.layer.addSublayer(gradient) let tap = UITapGestureRecognizer(target: self, action: #selector(touchUpInsideViewRichWorkspace(_:))) tap.delegate = self viewRichWorkspace?.addGestureRecognizer(tap) - viewSeparatorHeightConstraint.constant = 0.5 - viewSeparator.backgroundColor = .separator - + markdownParser = MarkdownParser(font: UIFont.systemFont(ofSize: 15), color: NCBrandColor.shared.textColor) markdownParser.header.font = UIFont.systemFont(ofSize: 25) if let richWorkspaceText = richWorkspaceText { @@ -131,19 +73,6 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat // labelSection.text = "" viewSectionHeightConstraint.constant = 0 - - buttonTransfer.backgroundColor = .clear - buttonTransfer.setImage(nil, for: .normal) - buttonTransfer.layer.cornerRadius = 6 - buttonTransfer.layer.masksToBounds = true - imageButtonTransfer.image = UIImage(systemName: "stop.circle") - imageButtonTransfer.tintColor = .white - labelTransfer.text = "" - progressTransfer.progress = 0 - progressTransfer.tintColor = NCBrandColor.shared.brand - progressTransfer.trackTintColor = NCBrandColor.shared.brand.withAlphaComponent(0.2) - transferSeparatorBottom.backgroundColor = .separator - transferSeparatorBottomHeightConstraint.constant = 0.5 } override func layoutSublayers(of layer: CALayer) { @@ -153,50 +82,6 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat setRichWorkspaceColor() } - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - - setRichWorkspaceColor() - } - - // MARK: - View - - func setStatusButtonsView(enable: Bool) { - - buttonSwitch.isEnabled = enable - buttonOrder.isEnabled = enable - buttonMore.isEnabled = enable - } - - func buttonMoreIsHidden(_ isHidden: Bool) { - buttonMore.isHidden = isHidden - } - - func setImageSwitchList() { - buttonSwitch.setImage(UIImage(systemName: "list.bullet")!.image(color: NCBrandColor.shared.iconColor, width: 20, height: 15), for: .normal) - } - - func setImageSwitchGrid() { - buttonSwitch.setImage(UIImage(systemName: "square.grid.2x2")!.image(color: NCBrandColor.shared.iconColor, size: 20), for: .normal) - } - - func setButtonsView(height: CGFloat) { - - viewButtonsViewHeightConstraint.constant = height - if height == 0 { - viewButtonsView.isHidden = true - } else { - viewButtonsView.isHidden = false - } - } - - func setSortedTitle(_ title: String) { - - let title = NSLocalizedString(title, comment: "") - buttonOrder.setTitle(title, for: .normal) - } - - // MARK: - RichWorkspace func setContent(heightHeaderRichWorkspace: CGFloat, richWorkspaceText: String?, heightHeaderRecommendations: CGFloat, @@ -204,11 +89,12 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat heightHeaderSection: CGFloat, sectionText: String?, viewController: UIViewController?, + sceneItentifier: String, delegate: NCSectionFirstHeaderDelegate?) { viewRichWorkspaceHeightConstraint.constant = heightHeaderRichWorkspace viewRecommendationsHeightConstraint.constant = heightHeaderRecommendations viewSectionHeightConstraint.constant = heightHeaderSection - + if let richWorkspaceText, richWorkspaceText != self.richWorkspaceText { textViewRichWorkspace.attributedText = markdownParser.parse(richWorkspaceText) self.richWorkspaceText = richWorkspaceText @@ -217,90 +103,39 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat self.recommendations = recommendations self.labelSection.text = sectionText self.viewController = viewController + self.sceneIdentifier = sceneItentifier self.delegate = delegate - + if heightHeaderRichWorkspace != 0, let richWorkspaceText, !richWorkspaceText.isEmpty { viewRichWorkspace.isHidden = false } else { viewRichWorkspace.isHidden = true } - } - - func setRichWorkspaceText(_ text: String?) { - guard let text = text else { return } - if text != self.richWorkspaceText { - textViewRichWorkspace.attributedText = markdownParser.parse(text) - self.richWorkspaceText = text + if heightHeaderRecommendations != 0 && !recommendations.isEmpty { + viewRecommendations.isHidden = false + } else { + viewRecommendations.isHidden = true } - } - - // MARK: - Transfer - func setViewTransfer(isHidden: Bool, ocId: String? = nil, text: String? = nil, progress: Float? = nil) { - labelTransfer.text = text - viewTransfer.isHidden = isHidden - progressTransfer.progress = 0 - - if isHidden { - viewTransferHeightConstraint.constant = 0 + if heightHeaderSection == 0 { + viewSection.isHidden = true } else { - var image: UIImage? - if let ocId, - let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) { -// image = utility.getIcon(metadata: metadata)?.darken() -// if image == nil { -// image = utility.loadImage(named: metadata.iconName, useTypeIconFile: true) - image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt256)?.darken() - if image == nil { - image = UIImage(named: metadata.iconName) - buttonTransfer.backgroundColor = .lightGray - } else { - buttonTransfer.backgroundColor = .clear - } - } - viewTransferHeightConstraint.constant = NCGlobal.shared.heightHeaderTransfer - if let progress { - progressTransfer.progress = progress - } + viewSection.isHidden = false } -// if heightHeaderSection == 0 { -// viewSection.isHidden = true -// } else { -// viewSection.isHidden = false -// } - self.collectionViewRecommendations.reloadData() } // MARK: - RichWorkspace - private func setRichWorkspaceColor() { - if traitCollection.userInterfaceStyle == .dark { - richWorkspaceGradient.colors = [UIColor(white: 0, alpha: 0).cgColor, UIColor.black.cgColor] + func setRichWorkspaceColor(style: UIUserInterfaceStyle? = nil) { + if let style { + richWorkspaceGradient.colors = style == .light ? [UIColor(white: 1, alpha: 0).cgColor, UIColor.white.cgColor] : [UIColor(white: 0, alpha: 0).cgColor, UIColor.black.cgColor] } else { - richWorkspaceGradient.colors = [UIColor(white: 1, alpha: 0).cgColor, UIColor.white.cgColor] + richWorkspaceGradient.colors = traitCollection.userInterfaceStyle == .light ? [UIColor(white: 1, alpha: 0).cgColor, UIColor.white.cgColor] : [UIColor(white: 0, alpha: 0).cgColor, UIColor.black.cgColor] } } - - // MARK: - Action - - @IBAction func touchUpInsideSwitch(_ sender: Any) { - delegate?.tapButtonSwitch(sender) - } - - @IBAction func touchUpInsideOrder(_ sender: Any) { - delegate?.tapButtonOrder(sender) - } - - @IBAction func touchUpInsideMore(_ sender: Any) { - delegate?.tapButtonMore(sender) - } - - @IBAction func touchUpTransfer(_ sender: Any) { - delegate?.tapButtonTransfer(sender) - } @objc func touchUpInsideViewRichWorkspace(_ sender: Any) { delegate?.tapRichWorkspace(sender) @@ -317,7 +152,7 @@ extension NCSectionFirstHeader: UICollectionViewDataSource { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? NCRecommendationsCell else { fatalError() } if let metadata = NCManageDatabase.shared.getMetadataFromFileId(recommendedFiles.id) { - let imagePreview = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt512) + let imagePreview = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: global.previewExt512, userId: metadata.userId, urlBase: metadata.urlBase) if metadata.directory { cell.image.image = self.utility.loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) @@ -329,20 +164,30 @@ extension NCSectionFirstHeader: UICollectionViewDataSource { cell.image.image = self.utility.loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) cell.image.contentMode = .scaleAspectFit if recommendedFiles.hasPreview { - NextcloudKit.shared.downloadPreview(fileId: metadata.fileId, account: metadata.account) { _, _, _, _, responseData, error in - if error == .success, let data = responseData?.data { - self.utility.createImageFileFrom(data: data, ocId: metadata.ocId, etag: metadata.etag) - if let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt512) { - for case let cell as NCRecommendationsCell in self.collectionViewRecommendations.visibleCells { - if cell.id == recommendedFiles.id { - cell.image.contentMode = .scaleAspectFill - if metadata.classFile == NKCommon.TypeClassFile.document.rawValue { - cell.setImageCorner(withBorder: true) + Task { + let resultsPreview = await NextcloudKit.shared.downloadPreviewAsync(fileId: metadata.fileId, etag: metadata.etag, account: metadata.account) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: metadata.account, + path: metadata.fileId, + name: "DownloadPreview") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + if resultsPreview.error == .success, let data = resultsPreview.responseData?.data { + self.utility.createImageFileFrom(data: data, ocId: metadata.ocId, etag: metadata.etag, userId: metadata.userId, urlBase: metadata.urlBase) + if let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: self.global.previewExt512, userId: metadata.userId, urlBase: metadata.urlBase) { + Task { @MainActor in + for case let cell as NCRecommendationsCell in self.collectionViewRecommendations.visibleCells { + if cell.id == recommendedFiles.id { + cell.image.contentMode = .scaleAspectFill + if metadata.classFile == NKTypeClassFile.document.rawValue { + cell.setImageCorner(withBorder: true) + } + UIView.transition(with: cell.image, duration: 0.75, options: .transitionCrossDissolve, animations: { + cell.image.image = image + }, completion: nil) + break } - UIView.transition(with: cell.image, duration: 0.75, options: .transitionCrossDissolve, animations: { - cell.image.image = image - }, completion: nil) - break } } } @@ -351,7 +196,7 @@ extension NCSectionFirstHeader: UICollectionViewDataSource { } } - if metadata.hasPreview, metadata.classFile == NKCommon.TypeClassFile.document.rawValue, imagePreview != nil { + if metadata.hasPreview, metadata.classFile == NKTypeClassFile.document.rawValue, imagePreview != nil { cell.setImageCorner(withBorder: true) } else { cell.setImageCorner(withBorder: false) @@ -383,18 +228,18 @@ extension NCSectionFirstHeader: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { let recommendedFiles = self.recommendations[indexPath.row] guard let metadata = NCManageDatabase.shared.getMetadataFromFileId(recommendedFiles.id), - metadata.classFile != NKCommon.TypeClassFile.url.rawValue, + metadata.classFile != NKTypeClassFile.url.rawValue, let viewController else { return nil } let identifier = indexPath as NSCopying - let image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal().previewExt1024) + let image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal().previewExt1024, userId: metadata.userId, urlBase: metadata.urlBase) #if EXTENSION return nil #else return UIContextMenuConfiguration(identifier: identifier, previewProvider: { - return NCViewerProviderContextMenu(metadata: metadata, image: image) + return NCViewerProviderContextMenu(metadata: metadata, image: image, sceneIdentifier: self.sceneIdentifier) }, actionProvider: { _ in let cell = collectionView.cellForItem(at: indexPath) let contextMenu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: viewController, sceneIdentifier: self.sceneIdentifier, image: image, sender: cell) @@ -413,7 +258,7 @@ extension NCSectionFirstHeader: UICollectionViewDelegateFlowLayout { } extension NCSectionFirstHeader: NCRecommendationsCellDelegate { - func touchUpInsideButtonMenu(with metadata: tableMetadata, image: UIImage?) { - self.delegate?.tapRecommendationsButtonMenu(with: metadata, image: image) + func touchUpInsideButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { + self.delegate?.tapRecommendationsButtonMenu(with: metadata, image: image, sender: sender) } } diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.xib b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.xib index 53ceade6de..1206a44a6a 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.xib +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.xib @@ -1,79 +1,24 @@ - + - - + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -81,139 +26,98 @@ - - - - - + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - + + - - - - - + + + + - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + - + - - - - - - + + + diff --git a/iOSClient/Menu/NCMenuAction.swift b/iOSClient/Menu/NCMenuAction.swift index 76ad642905..1391db46f5 100644 --- a/iOSClient/Menu/NCMenuAction.swift +++ b/iOSClient/Menu/NCMenuAction.swift @@ -110,7 +110,7 @@ extension NCMenuAction { static func deleteOrUnshareAction(selectedMetadatas: [tableMetadata], metadataFolder: tableMetadata? = nil, controller: NCMainTabBarController?, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { var titleDelete = NSLocalizedString("_delete_", comment: "") var message = NSLocalizedString("_want_delete_", comment: "") - var icon = "trash" + var icon = "trashIcon" var destructive = false var color = NCBrandColor.shared.iconImageColor @@ -166,8 +166,10 @@ extension NCMenuAction { /// Open "share view" (activity VC) to open files in another app static func share(selectedMetadatas: [tableMetadata], controller: NCMainTabBarController?, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { NCMenuAction( - title: NSLocalizedString("_share_", comment: ""), - icon: NCUtility().loadImage(named: "square.and.arrow.up", colors: [NCBrandColor.shared.iconImageColor]), +// title: NSLocalizedString("_share_", comment: ""), +// icon: NCUtility().loadImage(named: "share", colors: [NCBrandColor.shared.iconImageColor]), + title: NSLocalizedString("_open_in_", comment: ""), + icon: NCUtility().loadImage(named: "open_file",colors: [NCBrandColor.shared.iconImageColor]), order: order, sender: sender, action: { _ in @@ -183,7 +185,7 @@ extension NCMenuAction { static func setAvailableOfflineAction(selectedMetadatas: [tableMetadata], isAnyOffline: Bool, viewController: UIViewController, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { NCMenuAction( title: isAnyOffline ? NSLocalizedString("_remove_available_offline_", comment: "") : NSLocalizedString("_set_available_offline_", comment: ""), - icon: NCUtility().loadImage(named: "icloud.and.arrow.down", colors: [NCBrandColor.shared.iconImageColor]), + icon: NCUtility().loadImage(named: "cloudDownload", colors: [NCBrandColor.shared.iconImageColor]), order: order, sender: sender, action: { _ in @@ -217,24 +219,24 @@ extension NCMenuAction { } /// Copy files to pasteboard - static func copyAction(selectOcId: [String], viewController: UIViewController, order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction { + static func copyAction(fileSelect: [String], controller: NCMainTabBarController?, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { NCMenuAction( title: NSLocalizedString("_copy_file_", comment: ""), icon: NCUtility().loadImage(named: "copy", colors: [NCBrandColor.shared.iconImageColor]), order: order, + sender: sender, action: { _ in - NCActionCenter.shared.copyPasteboard(pasteboardOcIds: selectOcId, viewController: viewController) + NCDownloadAction.shared.copyPasteboard(pasteboardOcIds: fileSelect, controller: controller) completion?() } ) } - /// Open view that lets the user move or copy the files within Nextcloud static func moveOrCopyAction(selectedMetadatas: [tableMetadata], account: String, viewController: UIViewController, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { NCMenuAction( title: NSLocalizedString("_move_or_copy_", comment: ""), - icon: NCUtility().loadImage(named: "rectangle.portrait.and.arrow.right", colors: [NCBrandColor.shared.iconImageColor]), + icon: NCUtility().loadImage(named: "move", colors: [NCBrandColor.shared.iconImageColor]), order: order, sender: sender, action: { _ in @@ -287,4 +289,107 @@ extension NCMenuAction { } ) } + + /// Open "share view" (activity VC) to open files in another app + static func openInAction(selectedMetadatas: [tableMetadata], controller: NCMainTabBarController?, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { + NCMenuAction( + title: NSLocalizedString("_open_in_", comment: ""), + icon: NCUtility().loadImage(named: "open_file",colors: [NCBrandColor.shared.iconImageColor]), + order: order, + sender: sender, + action: { _ in + NCDownloadAction.shared.openActivityViewController(selectedMetadata: selectedMetadatas, controller: controller, sender: sender) + completion?() + } + ) + } + + /// Save selected files to user's photo library + static func saveMediaAction(selectedMediaMetadatas: [tableMetadata], controller: NCMainTabBarController?, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { + var title: String = NSLocalizedString("_save_selected_files_", comment: "") + var icon = NCUtility().loadImage(named: "save_files",colors: [NCBrandColor.shared.iconImageColor]) + if selectedMediaMetadatas.allSatisfy({ NCManageDatabase.shared.getMetadataLivePhoto(metadata: $0) != nil }) { + title = NSLocalizedString("_livephoto_save_", comment: "") + icon = NCUtility().loadImage(named: "livephoto") + } + + return NCMenuAction( + title: title, + icon: icon, + order: order, + sender: sender, + action: { _ in + for metadata in selectedMediaMetadatas { + if let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) { + NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV, hudView: controller?.view ?? UIView())) + } else { + if NCUtilityFileSystem().fileProviderStorageExists(metadata) { + NCDownloadAction.shared.saveAlbum(metadata: metadata, controller: controller) + } else { + if NCNetworking.shared.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty { + NCNetworking.shared.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum)) + } + } + } + } + completion?() + } + ) + } + + /// Open AirPrint view to print a single file + static func printAction(metadata: tableMetadata, order: Int = 0, sender: Any?) -> NCMenuAction { + NCMenuAction( + title: NSLocalizedString("_print_", comment: ""), + icon: NCUtility().loadImage(named: "printer", colors: [NCBrandColor.shared.iconImageColor]), + order: order, + sender: sender, + action: { _ in + if NCUtilityFileSystem().fileProviderStorageExists(metadata) { + metadata.sessionSelector = NCGlobal.shared.selectorPrint + NCDownloadAction.shared.downloadedFile(metadata: metadata, error: NKError()) + } else { + NCNetworking.shared.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorPrint)) + } + } + ) + } + + // MARK: - Print + + static func printDocument(metadata: tableMetadata) { + +// let fileNameURL = URL(fileURLWithPath: NCUtilityFileSystem().getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)) +// let printController = UIPrintInteractionController.shared +// let printInfo = UIPrintInfo(dictionary: nil) +// +// printInfo.jobName = fileNameURL.lastPathComponent +// printInfo.outputType = metadata.isImage ? .photo : .general +// printController.printInfo = printInfo +// printController.showsNumberOfCopies = true +// +// guard !UIPrintInteractionController.canPrint(fileNameURL) else { +// printController.printingItem = fileNameURL +// printController.present(animated: true) +// return +// } +// +// // can't print without data +// guard let data = try? Data(contentsOf: fileNameURL) else { return } +// +// if let svg = SVGKImage(data: data) { +// printController.printingItem = svg.uiImage +// printController.present(animated: true) +// return +// } +// +// guard let text = String(data: data, encoding: .utf8) else { return } +// let formatter = UISimpleTextPrintFormatter(text: text) +// formatter.perPageContentInsets.top = 72 +// formatter.perPageContentInsets.bottom = 72 +// formatter.perPageContentInsets.left = 72 +// formatter.perPageContentInsets.right = 72 +// printController.printFormatter = formatter +// printController.present(animated: true) + } } diff --git a/iOSClient/Menu/NCSortMenu.swift b/iOSClient/Menu/NCSortMenu.swift index f72dcb7197..ffb6f0b1d7 100644 --- a/iOSClient/Menu/NCSortMenu.swift +++ b/iOSClient/Menu/NCSortMenu.swift @@ -42,116 +42,130 @@ class NCSortMenu: NSObject { self.hideDirectoryOnTop = hideDirectoryOnTop self.account = account - guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: account, key: key, serverUrl: serverUrl) else { return } - var actions = [NCMenuAction]() - var title = "" - var icon = UIImage() - - if layoutForView.ascending { - title = NSLocalizedString("_order_by_name_z_a_", comment: "") - icon = UIImage(named: "sortFileNameZA")!.image(color: NCBrandColor.shared.iconColor, size: 50) - } else { - title = NSLocalizedString("_order_by_name_a_z_", comment: "") - icon = UIImage(named: "sortFileNameAZ")!.image(color: NCBrandColor.shared.iconColor, size: 50) - } - - actions.append( - NCMenuAction( - title: title, - icon: icon, - selected: layoutForView.sort == "fileName", - on: layoutForView.sort == "fileName", - action: { _ in - layoutForView.sort = "fileName" - layoutForView.ascending = !layoutForView.ascending - self.actionMenu(layoutForView: layoutForView) - } - ) - ) - - if layoutForView.ascending { - title = NSLocalizedString("_order_by_date_more_recent_", comment: "") - icon = UIImage(named: "sortDateMoreRecent")!.image(color: NCBrandColor.shared.iconColor, size: 50) - } else { - title = NSLocalizedString("_order_by_date_less_recent_", comment: "") - icon = UIImage(named: "sortDateLessRecent")!.image(color: NCBrandColor.shared.iconColor, size: 50) - } - - actions.append( - NCMenuAction( - title: title, - icon: icon, - selected: layoutForView.sort == "date", - on: layoutForView.sort == "date", - action: { _ in - layoutForView.sort = "date" - layoutForView.ascending = !layoutForView.ascending - self.actionMenu(layoutForView: layoutForView) - } - ) - ) - - if layoutForView.ascending { - title = NSLocalizedString("_order_by_size_largest_", comment: "") - icon = UIImage(named: "sortLargest")!.image(color: NCBrandColor.shared.iconColor, size: 50) - } else { - title = NSLocalizedString("_order_by_size_smallest_", comment: "") - icon = UIImage(named: "sortSmallest")!.image(color: NCBrandColor.shared.iconColor, size: 50) - } - - actions.append( - NCMenuAction( - title: title, - icon: icon, - selected: layoutForView.sort == "size", - on: layoutForView.sort == "size", - action: { _ in - layoutForView.sort = "size" - layoutForView.ascending = !layoutForView.ascending - self.actionMenu(layoutForView: layoutForView) - } - ) - ) - - if !hideDirectoryOnTop { - actions.append( - NCMenuAction( - title: NSLocalizedString("_directory_on_top_no_", comment: ""), - icon: UIImage(named: "foldersOnTop")!.image(color: NCBrandColor.shared.iconColor, size: 50), - selected: layoutForView.directoryOnTop, - on: layoutForView.directoryOnTop, - action: { _ in - layoutForView.directoryOnTop = !layoutForView.directoryOnTop +// guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: account, key: key, serverUrl: serverUrl) else { return } +// var actions = [NCMenuAction]() +// var title = "" +// var icon = UIImage() +// +// if layoutForView.ascending { +// title = NSLocalizedString("_order_by_name_z_a_", comment: "") +// icon = UIImage(named: "sortFileNameZA")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } else { +// title = NSLocalizedString("_order_by_name_a_z_", comment: "") +// icon = UIImage(named: "sortFileNameAZ")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// icon = UIImage(named: "sortFileNameZA")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } else { +// title = NSLocalizedString("_order_by_name_a_z_", comment: "") +// icon = UIImage(named: "sortFileNameAZ")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } +// +// actions.append( +// NCMenuAction( +// title: title, +// icon: icon, +// selected: layoutForView.sort == "fileName", +// on: layoutForView.sort == "fileName", +// action: { _ in +// layoutForView.sort = "fileName" +// layoutForView.ascending = !layoutForView.ascending +// self.actionMenu(layoutForView: layoutForView) +// } +// ) +// ) +// +// if layoutForView.ascending { +// title = NSLocalizedString("_order_by_date_more_recent_", comment: "") +// icon = UIImage(named: "sortDateMoreRecent")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } else { +// title = NSLocalizedString("_order_by_date_less_recent_", comment: "") +// icon = UIImage(named: "sortDateLessRecent")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// icon = UIImage(named: "sortDateMoreRecent")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } else { +// title = NSLocalizedString("_order_by_date_less_recent_", comment: "") +// icon = UIImage(named: "sortDateLessRecent")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } +// +// actions.append( +// NCMenuAction( +// title: title, +// icon: icon, +// selected: layoutForView.sort == "date", +// on: layoutForView.sort == "date", +// action: { _ in +// layoutForView.sort = "date" +// layoutForView.ascending = !layoutForView.ascending +// self.actionMenu(layoutForView: layoutForView) +// } +// ) +// ) +// +// if layoutForView.ascending { +// title = NSLocalizedString("_order_by_size_largest_", comment: "") +// icon = UIImage(named: "sortLargest")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } else { +// title = NSLocalizedString("_order_by_size_smallest_", comment: "") +// icon = UIImage(named: "sortSmallest")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// icon = UIImage(named: "sortLargest")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } else { +// title = NSLocalizedString("_order_by_size_smallest_", comment: "") +// icon = UIImage(named: "sortSmallest")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } +// +// actions.append( +// NCMenuAction( +// title: title, +// icon: icon, +// selected: layoutForView.sort == "size", +// on: layoutForView.sort == "size", +// action: { _ in +// layoutForView.sort = "size" +// layoutForView.ascending = !layoutForView.ascending +// self.actionMenu(layoutForView: layoutForView) +// } +// ) +// ) +// +// if !hideDirectoryOnTop { +// actions.append( +// NCMenuAction( +// title: NSLocalizedString("_directory_on_top_no_", comment: ""), +// icon: UIImage(named: "foldersOnTop")!.image(color: NCBrandColor.shared.iconImageColor, size: 50), +// icon: UIImage(named: "foldersOnTop")!.image(color: NCBrandColor.shared.iconImageColor, size: 50), +// selected: layoutForView.directoryOnTop, +// on: layoutForView.directoryOnTop, +// action: { _ in +// layoutForView.directoryOnTop = !layoutForView.directoryOnTop // NCKeychain().setDirectoryOnTop(account: self.account, value: layoutForView.directoryOnTop) - self.actionMenu(layoutForView: layoutForView) - } - ) - ) - } - - viewController.presentMenu(with: actions) +//// NCKeychain().setDirectoryOnTop(account: self.account, value: layoutForView.directoryOnTop) +// self.actionMenu(layoutForView: layoutForView) +// } +// ) +// ) +// } +// +// viewController.presentMenu(with: actions) } - +// func actionMenu(layoutForView: NCDBLayoutForView) { - - switch layoutForView.sort { - case "fileName": - layoutForView.titleButtonHeader = layoutForView.ascending ? "_sorted_by_name_a_z_" : "_sorted_by_name_z_a_" - case "date": - layoutForView.titleButtonHeader = layoutForView.ascending ? "_sorted_by_date_less_recent_" : "_sorted_by_date_more_recent_" - case "size": - layoutForView.titleButtonHeader = layoutForView.ascending ? "_sorted_by_size_smallest_" : "_sorted_by_size_largest_" - default: - break - } - - self.sortButton?.setTitle(NSLocalizedString(layoutForView.titleButtonHeader, comment: ""), for: .normal) - NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView) - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeLayout, - object: nil, - userInfo: ["account": self.account, - "serverUrl": self.serverUrl, - "layoutForView": layoutForView]) - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) +// +// switch layoutForView.sort { +// case "fileName": +// layoutForView.titleButtonHeader = layoutForView.ascending ? "_sorted_by_name_a_z_" : "_sorted_by_name_z_a_" +// case "date": +// layoutForView.titleButtonHeader = layoutForView.ascending ? "_sorted_by_date_less_recent_" : "_sorted_by_date_more_recent_" +// case "size": +// layoutForView.titleButtonHeader = layoutForView.ascending ? "_sorted_by_size_smallest_" : "_sorted_by_size_largest_" +// default: +// break +// } +// +// self.sortButton?.setTitle(NSLocalizedString(layoutForView.titleButtonHeader, comment: ""), for: .normal) +// NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView) +// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeLayout, +// object: nil, +// userInfo: ["account": self.account, +// "serverUrl": self.serverUrl, +// "layoutForView": layoutForView]) +// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) } } diff --git a/iOSClient/Networking/NCDownloadAction.swift b/iOSClient/Networking/NCDownloadAction.swift index 082ceb63b9..982aca2a66 100644 --- a/iOSClient/Networking/NCDownloadAction.swift +++ b/iOSClient/Networking/NCDownloadAction.swift @@ -120,6 +120,14 @@ class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSel self.openActivityViewController(selectedMetadata: [metadata], controller: controller, sender: nil) + case NCGlobal.shared.selectorPrint: + // waiting close menu + // https://github.com/nextcloud/ios/issues/2278 +// DispatchQueue.main.async { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + self.printDocument(metadata: metadata) + } + case NCGlobal.shared.selectorSaveAlbum: self.saveAlbum(metadata: metadata, controller: controller) @@ -160,7 +168,7 @@ class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSel if let metadata = await NCManageDatabase.shared.getMetadataLivePhotoAsync(metadata: metadata) { metadatasSynchronizationOffline.append(metadata) } - await NCManageDatabase.shared.addLocalFilesAsync(metadatas: [metadata], offline: true) + await NCManageDatabase.shared.addLocalFileAsync(metadata: metadata, offline: true) for metadata in metadatasSynchronizationOffline { await NCManageDatabase.shared.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, session: NCNetworking.shared.sessionDownloadBackground, @@ -257,7 +265,7 @@ class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSel etag: download.etag) if download.nkError == .success { - await NCManageDatabase.shared.addLocalFilesAsync(metadatas: [metadata]) + await NCManageDatabase.shared.addLocalFileAsync(metadata: metadata) if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: viewController) { viewController.navigationController?.pushViewController(vc, animated: true) } @@ -275,51 +283,10 @@ class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSel Task { @MainActor in NCActivityIndicator.shared.stop() - if let metadata = metadata, let file = file, error == .success { - // Remove all known download limits from shares related to the given file. - // This avoids obsolete download limit objects to stay around. - // Afterwards create new download limits, should any such be returned for the known shares. - let shares = await NCManageDatabase.shared.getTableSharesAsync(account: metadata.account, - serverUrl: metadata.serverUrl, - fileName: metadata.fileName) - for share in shares { - await NCManageDatabase.shared.deleteDownloadLimitAsync(byAccount: metadata.account, shareToken: share.token) - - if let receivedDownloadLimit = file.downloadLimits.first(where: { $0.token == share.token }) { - await NCManageDatabase.shared.createDownloadLimitAsync(account: metadata.account, - count: receivedDownloadLimit.count, - limit: receivedDownloadLimit.limit, - token: receivedDownloadLimit.token) - } - } - - var pages: [NCBrandOptions.NCInfoPagingTab] = [] + if let metadata = metadata, error == .success { let shareNavigationController = UIStoryboard(name: "NCShare", bundle: nil).instantiateInitialViewController() as? UINavigationController - let shareViewController = shareNavigationController?.topViewController as? NCSharePaging - - for value in NCBrandOptions.NCInfoPagingTab.allCases { - pages.append(value) - } - if capabilities.activity.isEmpty, let idx = pages.firstIndex(of: .activity) { - pages.remove(at: idx) - } - if !metadata.isSharable(), let idx = pages.firstIndex(of: .sharing) { - pages.remove(at: idx) - } - - (pages, page) = NCApplicationHandle().filterPages(pages: pages, page: page, metadata: metadata) - - shareViewController?.pages = pages + let shareViewController = shareNavigationController?.topViewController as? NCShare shareViewController?.metadata = metadata - - if pages.contains(page) { - shareViewController?.page = page - } else if let page = pages.first { - shareViewController?.page = page - } else { - return - } - shareNavigationController?.modalPresentationStyle = .formSheet if let shareNavigationController = shareNavigationController { viewController.present(shareNavigationController, animated: true, completion: nil) @@ -461,14 +428,12 @@ class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSel } } } - + // MARK: - Copy & Paste - func copyPasteboard(pasteboardOcIds: [String], viewController: UIViewController) { + func copyPasteboard(pasteboardOcIds: [String], controller: NCMainTabBarController?) { var items = [[String: Any]]() - guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } - let hudView = viewController.view - var fractionCompleted: Float = 0 + let hudView = controller // getting file data can take some time and block the main queue DispatchQueue.global(qos: .userInitiated).async { @@ -482,37 +447,34 @@ class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSel } } - // do 5 downloads in parallel to optimize efficiency - let processor = ParallelWorker(n: 5, titleKey: "_downloading_", totalTasks: downloadMetadatas.count, hudView: hudView) - + let processor = ParallelWorker(n: 5, titleKey: "_downloading_", totalTasks: downloadMetadatas.count, controller: controller) for metadata in downloadMetadatas { processor.execute { completion in - guard let metadata = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata], - session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload, - selector: "") else { return completion() } - NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: false) { - } requestHandler: { _ in - } progressHandler: { progress in - if Float(progress.fractionCompleted) > fractionCompleted || fractionCompleted == 0 { - processor.hud?.progress = Float(progress.fractionCompleted) - fractionCompleted = Float(progress.fractionCompleted) + Task { + guard let metadata = await NCManageDatabase.shared.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, + session: NCNetworking.shared.sessionDownload, + selector: "", + sceneIdentifier: controller?.sceneIdentifier) else { + return completion() } - } completion: { _, _ in - fractionCompleted = 0 + + await NCNetworking.shared.downloadFile(metadata: metadata) { _ in + } progressHandler: { progress in + processor.hud.progress(progress.fractionCompleted) + } + completion() } } } + processor.completeWork { items.append(contentsOf: downloadMetadatas.compactMap({ $0.toPasteBoardItem() })) UIPasteboard.general.setItems(items, options: [:]) } } } - - - // MARK: - Copy & Paste - + func pastePasteboard(serverUrl: String, account: String, controller: NCMainTabBarController?) async { var fractionCompleted: Float = 0 let processor = ParallelWorker(n: 5, titleKey: "_status_uploading_", totalTasks: nil, controller: controller) @@ -633,7 +595,7 @@ class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSel // MARK: - NCSelect + Delegate - func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], overwrite: Bool, copy: Bool, move: Bool, session: NCSession.Session) { + func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], overwrite: Bool, copy: Bool, move: Bool) {//, session: NCSession.Session) { if let destination = serverUrl, !items.isEmpty { if copy { for case let metadata as tableMetadata in items { @@ -716,13 +678,56 @@ class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSel controller?.present(navigationController, animated: true, completion: nil) } } -} + + // MARK: - Print + + func printDocument(metadata: tableMetadata) { + + let fileNameURL = URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase)) + let printController = UIPrintInteractionController.shared + let printInfo = UIPrintInfo(dictionary: nil) + + printInfo.jobName = fileNameURL.lastPathComponent + printInfo.outputType = metadata.isImage ? .photo : .general + printController.printInfo = printInfo + printController.showsNumberOfCopies = true + + guard !UIPrintInteractionController.canPrint(fileNameURL) else { + printController.printingItem = fileNameURL + printController.present(animated: true) + return + } + + // can't print without data + guard let data = try? Data(contentsOf: fileNameURL) else { return } + if let svg = SVGKImage(data: data) { + printController.printingItem = svg.uiImage + printController.present(animated: true) + return + } + + guard let text = String(data: data, encoding: .utf8) else { return } + let formatter = UISimpleTextPrintFormatter(text: text) + formatter.perPageContentInsets.top = 72 + formatter.perPageContentInsets.bottom = 72 + formatter.perPageContentInsets.left = 72 + formatter.perPageContentInsets.right = 72 + printController.printFormatter = formatter + printController.present(animated: true) + } +} fileprivate extension tableMetadata { func toPasteBoardItem() -> [String: Any]? { // Get Data - let fileUrl = URL(fileURLWithPath: NCUtilityFileSystem().getDirectoryProviderStorageOcId(ocId, fileNameView: fileNameView)) + let fileUrl = URL(fileURLWithPath: NCUtilityFileSystem().getDirectoryProviderStorageOcId(ocId, + fileName: fileNameView, + userId: userId, + urlBase: urlBase)) guard NCUtilityFileSystem().fileProviderStorageExists(self), let data = try? Data(contentsOf: fileUrl) else { return nil } diff --git a/iOSClient/Offline/NCOffline.swift b/iOSClient/Offline/NCOffline.swift index 637d396943..2aca7501ca 100644 --- a/iOSClient/Offline/NCOffline.swift +++ b/iOSClient/Offline/NCOffline.swift @@ -34,8 +34,7 @@ class NCOffline: NCCollectionViewCommon { layoutKey = NCGlobal.shared.layoutViewOffline enableSearchBar = false headerRichWorkspaceDisable = true - emptyImageName = "folder_nmcloud" - emptyImage = UIImage(named: "folder_nmcloud") + emptyImageName = "cloudDownload" emptyTitle = "_files_no_files_" emptyDescription = "_tutorial_offline_view_" emptyDataPortaitOffset = 30