From 4f99972607708ad42c77400038b62421358d4006 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 | 209 +++-- .../Cell/NCCellProtocol.swift | 142 ++++ ...ctionViewCommon+SelectTabBarDelegate.swift | 163 +++- ...mmon+SwipeCollectionViewCellDelegate.swift | 94 +++ .../NCSectionFirstHeader.swift | 195 ++++- .../NCSectionFirstHeader.xib | 232 ++++-- iOSClient/Menu/NCMenuAction.swift | 290 +++++++ iOSClient/Menu/NCSortMenu.swift | 149 ++++ iOSClient/Networking/NCDownloadAction.swift | 735 ++++++++++++++++++ iOSClient/Offline/NCOffline.swift | 1 + 11 files changed, 2034 insertions(+), 177 deletions(-) create mode 100644 iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift create mode 100644 iOSClient/Main/Collection Common/NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift create mode 100644 iOSClient/Menu/NCMenuAction.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 4a020f53af..86922bc700 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 68c13890b2..6b7f82bf72 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -8,6 +8,8 @@ import RealmSwift import SwiftUI class NCFiles: NCCollectionViewCommon { + @IBOutlet weak var plusButton: UIButton! + internal var fileNameBlink: String? internal var lastOffsetY: CGFloat = 0 internal var lastScrollTime: TimeInterval = 0 @@ -29,14 +31,25 @@ 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])) + + 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) } } @@ -48,47 +61,43 @@ class NCFiles: NCCollectionViewCommon { } 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() } @@ -99,6 +108,7 @@ class NCFiles: NCCollectionViewCommon { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + resetPlusButtonAlpha() Task { await self.reloadDataSource() } @@ -138,6 +148,31 @@ class NCFiles: NCCollectionViewCommon { fileNameBlink = 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 { @@ -146,38 +181,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.getMetadatasAsyncDataSource(withServerUrl: self.serverUrl, - withUserId: self.session.userId, - withAccount: self.session.account, - withLayout: self.layoutForView) + 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) + 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()) @@ -190,7 +218,7 @@ class NCFiles: NCCollectionViewCommon { return } - 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 } @@ -203,7 +231,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) } } } @@ -240,12 +268,9 @@ class NCFiles: NCCollectionViewCommon { 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: account, serverUrl: serverUrl) let tableDirectory = await self.database.getTableDirectoryAsync(ocId: metadata.ocId) @@ -256,14 +281,14 @@ class NCFiles: NCCollectionViewCommon { 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() @@ -285,7 +310,6 @@ class NCFiles: NCCollectionViewCommon { guard resultsReadFolder.error == .success else { return(nil, resultsReadFolder.error, reloadRequired) } - reloadRequired = true if let metadataFolder { self.metadataFolder = metadataFolder.detachedCopy() @@ -351,8 +375,7 @@ class NCFiles: NCCollectionViewCommon { if error != .success { navigationController?.popViewController(animated: false) } - - return (metadatas, error, reloadRequired) + return (metadatas, error, true) } func blinkCell(fileName: String?) { @@ -382,9 +405,39 @@ class NCFiles: NCCollectionViewCommon { await didSelectMetadata(metadata, withOcIds: false) } + 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 { @@ -406,8 +459,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 new file mode 100644 index 0000000000..9e969faed9 --- /dev/null +++ b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift @@ -0,0 +1,142 @@ +// +// NCCellProtocol.swift +// Nextcloud +// +// Created by Philippe Weidmann on 05.06.20. +// 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 + +protocol NCCellProtocol { + var fileAvatarImageView: UIImageView? { get } + var fileAccount: String? { get set } + var fileOcId: String? { get set } + var fileOcIdTransfer: String? { get set } + var filePreviewImageView: UIImageView? { get set } + var fileUser: String? { get set } + var fileTitleLabel: UILabel? { get set } + var fileInfoLabel: UILabel? { get set } + var fileSubinfoLabel: UILabel? { get set } + var fileStatusImage: UIImageView? { get set } + var fileLocalImage: UIImageView? { get set } + var fileFavoriteImage: UIImageView? { get set } + 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() + func writeInfoDateSize(date: NSDate, size: Int64) + func setButtonMore(image: UIImage) + func hideImageItem(_ status: Bool) + func hideImageFavorite(_ status: Bool) + func hideImageStatus(_ status: Bool) + func hideImageLocal(_ status: Bool) + func hideLabelTitle(_ status: Bool) + func hideLabelInfo(_ status: Bool) + func hideLabelSubinfo(_ status: Bool) + func hideButtonShare(_ status: Bool) + func hideButtonMore(_ status: Bool) + func selected(_ status: Bool, isEditMode: Bool) + func setAccessibility(label: String, value: String) + func setTags(tags: [String]) + func setIconOutlines() +} + +extension NCCellProtocol { + var fileAvatarImageView: UIImageView? { + return nil + } + var fileAccount: String? { + get { return nil } + set {} + } + var fileOcId: String? { + get { return nil } + set {} + } + var fileOcIdTransfer: String? { + get { return nil } + set {} + } + var filePreviewImageView: UIImageView? { + get { return nil } + set {} + } + var fileTitleLabel: UILabel? { + get { return nil } + set {} + } + var fileInfoLabel: UILabel? { + get { return nil } + set { } + } + var fileSubinfoLabel: UILabel? { + get { return nil } + set { } + } + var fileStatusImage: UIImageView? { + get { return nil } + set {} + } + var fileLocalImage: UIImageView? { + get { return nil } + set {} + } + var fileFavoriteImage: UIImageView? { + get { return nil } + set {} + } + var fileSharedImage: UIImageView? { + get { return nil } + set {} + } + var fileMoreImage: UIImageView? { + get { return nil } + set {} + } + var cellSeparatorView: UIView? { + get { return nil } + set {} + } + var fileSharedLabel: UILabel? { + get { return nil } + set { } + } + + func titleInfoTrailingDefault() {} + func titleInfoTrailingFull() {} + func writeInfoDateSize(date: NSDate, size: Int64) {} + func setButtonMore(image: UIImage) {} + func hideImageItem(_ status: Bool) {} + func hideImageFavorite(_ status: Bool) {} + func hideImageStatus(_ status: Bool) {} + func hideImageLocal(_ status: Bool) {} + func hideLabelTitle(_ status: Bool) {} + func hideLabelInfo(_ status: Bool) {} + func hideLabelSubinfo(_ status: Bool) {} + func hideButtonShare(_ status: Bool) {} + func hideButtonMore(_ status: Bool) {} + func selected(_ status: Bool, isEditMode: Bool) {} + func setAccessibility(label: String, value: String) {} + func setTags(tags: [String]) {} + func setIconOutlines() {} +} diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift index 063f90b611..9460caf1a3 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 @@ -204,10 +223,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/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index 7272cffaea..865bbd1557 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -1,21 +1,64 @@ -// 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) } +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! @@ -51,6 +94,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 @@ -79,6 +134,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) { @@ -88,6 +156,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, @@ -100,7 +209,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 @@ -111,17 +220,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 { @@ -144,13 +284,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) @@ -167,7 +321,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) @@ -180,17 +334,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.metadata?.fileId == recommendedFiles.id { @@ -247,7 +394,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 new file mode 100644 index 0000000000..76ad642905 --- /dev/null +++ b/iOSClient/Menu/NCMenuAction.swift @@ -0,0 +1,290 @@ +// +// NCMenuAction.swift +// Nextcloud +// +// Created by Henrik Storch on 17.02.22. +// Copyright © 2022 Marino Faggiana. All rights reserved. +// +// Author Henrik Storch +// 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 UIKit +import NextcloudKit + +@available(*, deprecated, message: "Change to using iOS native context menus, as well as using ContextMenuActions and NCViewerContextMenu") +class NCMenuAction { + let accessibilityIdentifier: String? + let title: String + let boldTitle: Bool + let details: String? + let icon: UIImage + let selectable: Bool + var onTitle: String? + var onIcon: UIImage? + let destructive: Bool + var selected: Bool = false + var isOn: Bool = false + var action: ((_ menuAction: NCMenuAction) -> Void)? + var rowHeight: CGFloat { self.title == NCMenuAction.seperatorIdentifier ? NCMenuAction.seperatorHeight : self.details != nil ? 76 : 60 } + var order: Int = 0 + var sender: Any? + + init(title: String, boldTitle: Bool = false, destructive: Bool = false, details: String? = nil, icon: UIImage, order: Int = 0, accessibilityIdentifier: String? = nil, sender: Any?, action: ((_ menuAction: NCMenuAction) -> Void)?) { + self.accessibilityIdentifier = accessibilityIdentifier + self.title = title + self.boldTitle = boldTitle + self.destructive = destructive + self.details = details + self.icon = icon + self.action = action + self.selectable = false + self.order = order + self.sender = sender + } + + init(title: String, boldTitle: Bool = false, destructive: Bool = false, details: String? = nil, icon: UIImage, onTitle: String? = nil, onIcon: UIImage? = nil, selected: Bool, on: Bool, order: Int = 0, accessibilityIdentifier: String? = nil, sender: Any?, action: ((_ menuAction: NCMenuAction) -> Void)?) { + self.accessibilityIdentifier = accessibilityIdentifier + self.title = title + self.boldTitle = boldTitle + self.destructive = destructive + self.details = details + self.icon = icon + self.onTitle = onTitle ?? title + self.onIcon = onIcon ?? icon + self.action = action + self.selected = selected + self.isOn = on + self.selectable = true + self.order = order + self.sender = sender + } +} + +// MARK: - Actions + +extension NCMenuAction { + static let seperatorIdentifier = "NCMenuAction.SEPARATOR" + static let seperatorHeight: CGFloat = 0.5 + + /// A static seperator, with no actions, text, or icons + static func seperator(order: Int = 0, sender: Any?) -> NCMenuAction { + return NCMenuAction(title: seperatorIdentifier, icon: UIImage(), order: order, sender: sender, action: nil) + } + + /// Select all items + static func selectAllAction(sender: Any?, action: @escaping () -> Void) -> NCMenuAction { + NCMenuAction( + title: NSLocalizedString("_select_all_", comment: ""), + icon: NCUtility().loadImage(named: "checkmark.circle.fill", colors: [NCBrandColor.shared.iconImageColor]), + sender: sender, + action: { _ in action() } + ) + } + + /// Cancel + static func cancelAction(sender: Any?, action: @escaping () -> Void) -> NCMenuAction { + NCMenuAction( + title: NSLocalizedString("_cancel_", comment: ""), + icon: NCUtility().loadImage(named: "xmark", colors: [NCBrandColor.shared.iconImageColor]), + sender: sender, + action: { _ in action() } + ) + } + + /// Delete files either from cache or from Nextcloud, or unshare (depending on context) + 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 destructive = false + var color = NCBrandColor.shared.iconImageColor + + if selectedMetadatas.count > 1 { + titleDelete = NSLocalizedString("_delete_selected_files_", comment: "") + destructive = true + } else if let metadata = selectedMetadatas.first { + if NCManageDatabase.shared.isMetadataShareOrMounted(metadata: metadata, metadataFolder: metadataFolder) { + titleDelete = NSLocalizedString("_leave_share_", comment: "") + message = NSLocalizedString("_want_leave_share_", comment: "") + icon = "person.2.slash" + } else if metadata.directory { + titleDelete = NSLocalizedString("_delete_folder_", comment: "") + destructive = true + } else { + titleDelete = NSLocalizedString("_delete_file_", comment: "") + destructive = true + } + + if let metadataFolder = metadataFolder { + let isShare = metadata.permissions.contains(NCMetadataPermissions.permissionShared) && !metadataFolder.permissions.contains(NCMetadataPermissions.permissionShared) + let isMounted = metadata.permissions.contains(NCMetadataPermissions.permissionMounted) && !metadataFolder.permissions.contains(NCMetadataPermissions.permissionMounted) + if isShare || isMounted { + titleDelete = NSLocalizedString("_leave_share_", comment: "") + icon = "person.2.slash" + } + } + } // else: no metadata selected + + let canDeleteServer = selectedMetadatas.allSatisfy { !$0.lock } + var fileList = "" + for (ix, metadata) in selectedMetadatas.enumerated() { + guard ix < 3 else { fileList += "\n - ..."; break } + fileList += "\n - " + metadata.fileNameView + } + if destructive { color = .red } + + return NCMenuAction( + title: titleDelete, + destructive: destructive, + icon: NCUtility().loadImage(named: icon, colors: [color]), + order: order, + sender: sender, + action: { _ in + let alertController = UIAlertController.deleteFileOrFolder(titleString: titleDelete + "?", message: message + fileList, canDeleteServer: canDeleteServer, selectedMetadatas: selectedMetadatas, sceneIdentifier: controller?.sceneIdentifier) { _ in + completion?() + } + + controller?.present(alertController, animated: true, completion: nil) + }) + } + + /// 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]), + order: order, + sender: sender, + action: { _ in + Task { + await NCCreate().createActivityViewController(selectedMetadata: selectedMetadatas, controller: controller, sender: sender) + completion?() + } + } + ) + } + + /// Set (or remove) a file as *available offline*. Downloads the file if not downloaded already + 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]), + order: order, + sender: sender, + action: { _ in + if !isAnyOffline, selectedMetadatas.count > 3 { + let alert = UIAlertController( + title: NSLocalizedString("_set_available_offline_", comment: ""), + message: NSLocalizedString("_select_offline_warning_", comment: ""), + preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("_continue_", comment: ""), style: .default, handler: { _ in + Task { + for metadata in selectedMetadatas { + await NCNetworking.shared.setMetadataAvalableOffline(metadata, isOffline: isAnyOffline) + + } + completion?() + } + })) + alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel)) + viewController.present(alert, animated: true) + } else { + Task { + for metadata in selectedMetadatas { + await NCNetworking.shared.setMetadataAvalableOffline(metadata, isOffline: isAnyOffline) + + } + completion?() + } + } + } + ) + } + + /// 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( + title: NSLocalizedString("_move_or_copy_", comment: ""), + icon: NCUtility().loadImage(named: "rectangle.portrait.and.arrow.right", colors: [NCBrandColor.shared.iconImageColor]), + order: order, + sender: sender, + action: { _ in + Task { @MainActor in + var fileNameError: NKError? + let capabilities = await NKCapabilities.shared.getCapabilities(for: account) + + for metadata in selectedMetadatas { + if let sceneIdentifier = metadata.sceneIdentifier, + let controller = SceneManager.shared.getController(sceneIdentifier: sceneIdentifier), + let checkError = FileNameValidator.checkFileName(metadata.fileNameView, account: controller.account, capabilities: capabilities) { + + fileNameError = checkError + break + } + } + + if let fileNameError { + let message = "\(fileNameError.errorDescription) \(NSLocalizedString("_please_rename_file_", comment: ""))" + await UIAlertController.warningAsync( message: message, presenter: viewController) + } else { + let controller = viewController.tabBarController as? NCMainTabBarController + NCSelectOpen.shared.openView(items: selectedMetadatas, controller: controller) + } + completion?() + } + } + ) + } + + /// Lock or unlock a file using *files_lock* + static func lockUnlockFiles(shouldLock: Bool, metadatas: [tableMetadata], order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { + let titleKey: String + if metadatas.count == 1 { + titleKey = shouldLock ? "_lock_file_" : "_unlock_file_" + } else { + titleKey = shouldLock ? "_lock_selected_files_" : "_unlock_selected_files_" + } + let imageName = !shouldLock ? "lock_open" : "lock" + return NCMenuAction( + title: NSLocalizedString(titleKey, comment: ""), + icon: NCUtility().loadImage(named: imageName, colors: [NCBrandColor.shared.iconImageColor]), + order: order, + sender: sender, + action: { _ in + for metadata in metadatas where metadata.lock != shouldLock { + NCNetworking.shared.lockUnlockFile(metadata, shoulLock: shouldLock) + } + completion?() + } + ) + } +} 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 30387bf9e7e3833573bedaa5d383b890406389b5 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 14 Apr 2025 16:33:57 +0530 Subject: [PATCH 2/5] NMC 2172 - Dashboard theming customisation changes --- .../Cell/NCCellProtocol.swift | 20 ++++++-- .../NCSectionFirstHeader.swift | 48 ++++++++++++------- .../NCSectionFirstHeader.xib | 4 ++ iOSClient/Menu/NCSortMenu.swift | 22 ++++++--- iOSClient/Offline/NCOffline.swift | 4 +- 5 files changed, 67 insertions(+), 31 deletions(-) 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/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index 865bbd1557..51ae38a961 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) @@ -36,12 +37,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! @@ -62,11 +65,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() @@ -97,10 +99,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) @@ -110,7 +113,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 { @@ -139,12 +144,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 } @@ -168,16 +173,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) { @@ -250,9 +258,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 @@ -264,11 +272,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 +// } #if EXTENSION self.collectionViewRecommendations.reloadData() @@ -301,6 +309,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 23e8c92ab82476723906f2a9f2c880a524e0bd2d Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 6 Oct 2025 12:02:07 +0530 Subject: [PATCH 3/5] NMC 2172 - Dashboard theming customisation changes --- iOSClient/Files/NCFiles.swift | 370 ++++++++++-------- .../Cell/NCCellProtocol.swift | 1 + ...mmon+SwipeCollectionViewCellDelegate.swift | 6 +- .../NCSectionFirstHeader.swift | 84 ++-- iOSClient/Menu/NCSortMenu.swift | 2 +- 5 files changed, 227 insertions(+), 236 deletions(-) diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index 6b7f82bf72..5b8d99e0a2 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 @@ -11,6 +30,11 @@ 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 @@ -36,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 @@ -52,14 +73,7 @@ class NCFiles: NCCollectionViewCommon { self.plusButton.backgroundColor = NCBrandColor.shared.getElement(account: activeTableAccount.account) } } - - NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in - Task { - await self.stopSyncMetadata() - await self.searchOperationHandle.cancel() - } - } - + if self.serverUrl.isEmpty { /// @@ -73,6 +87,7 @@ class NCFiles: NCCollectionViewCommon { if let controller = userInfo["controller"] as? NCMainTabBarController, controller == self.controller { controller.account = account + controller.availableNotifications = false } else { return } @@ -84,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 @@ -97,110 +113,118 @@ 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) { super.viewDidAppear(animated) if !self.dataSource.isEmpty() { - blinkCell(fileName: self.fileNameBlink) - fileNameBlink = nil + self.blinkCell(fileName: self.fileNameBlink) + self.openFile(fileName: self.fileNameOpen) + self.fileNameBlink = nil + self.fileNameOpen = nil } Task { // Plus Menu reload - await self.mainNavigationController?.menuPlus?.create(session: session) - + let capabilities = await database.getCapabilities(account: self.session.account) ?? NKCapabilities.Capabilities() + await mainNavigationController?.createPlusMenu(session: self.session, capabilities: capabilities) // Server data if !isSearchingMode { await getServerData() } } - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - Task { - await stopSyncMetadata() - await NCNetworking.shared.networkingTasks.cancel(identifier: "\(self.serverUrl)_NCFiles") - } + self.showTipAutoUpload() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) fileNameBlink = nil + 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) + 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() + } + + var predicate = self.defaultPredicate + let predicateDirectory = NSPredicate(format: "account == %@ AND serverUrl == %@", session.account, self.serverUrl) + let dataSourceMetadatas = self.dataSource.getMetadatas() - 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 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 - cachingAsync(metadatas: metadatas) + let metadatas = self.database.getResultsMetadatasPredicate(predicate, layoutForView: layoutForView) + self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, layoutForView: layoutForView) + + 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 { @@ -211,59 +235,58 @@ class NCFiles: NCCollectionViewCommon { startSyncMetadata(metadatas: self.dataSource.getMetadatas()) } - await networking.networkingTasks.cancel(identifier: "\(self.serverUrl)_NCFiles") - + Task { + await networking.networkingTasks.cancel(identifier: "\(self.serverUrl)_NCFiles") + } guard !isSearchingMode else { - await self.search() - return + return networkSearch() } - let resultsReadFolder = await networkReadFolderAsync(serverUrl: self.serverUrl, refresh: refresh) - guard resultsReadFolder.error == .success, resultsReadFolder.reloadRequired else { - return - } + func downloadMetadata(_ metadata: tableMetadata) -> Bool { + let fileSize = utilityFileSystem.fileProviderStorageSize(metadata.ocId, fileNameView: metadata.fileNameView) + guard fileSize > 0 else { return false } - let metadatasForDownload: [tableMetadata] = resultsReadFolder.metadatas ?? self.dataSource.getMetadatas() - Task.detached(priority: .utility) { - for metadata in metadatasForDownload where !metadata.directory { - if await self.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 let localFile = database.getResultsTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))?.first { + if localFile.etag != metadata.etag { + return true } } + return false } - await self.reloadDataSource() - } + DispatchQueue.global().async { + self.networkReadFolder { metadatas, isChanged, error in + DispatchQueue.main.async { + self.refreshControl.endRefreshing() - private 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 isChanged || self.isNumberOfItemsInAllSectionsNull { + self.reloadDataSource() + } + } - if let tblLocalFile = await database.getTableLocalFileAsync(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) { - if tblLocalFile.etag != metadata.etag { - return true + 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) + } + } + } } } - return false } - private func networkReadFolderAsync(serverUrl: String, forced: Bool) async -> (metadatas: [tableMetadata]?, error: NKError, reloadRequired: Bool) { - var reloadRequired: Bool = false - let account = session.account - let resultsReadFile = await NCNetworking.shared.readFileAsync(serverUrlFileName: serverUrl, account: account) { task in - Task { - await NCNetworking.shared.networkingTasks.track(identifier: "\(self.serverUrl)_NCFiles", task: task) - } + 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() } @@ -272,14 +295,9 @@ class NCFiles: NCCollectionViewCommon { return (nil, resultsReadFile.error, false) } - await self.database.updateDirectoryRichWorkspaceAsync(metadata.richWorkspace, account: account, serverUrl: serverUrl) + 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: account) - await NCManageDatabase.shared.deleteLivePhotoError() - let shouldSkipUpdate: Bool = ( !refresh && tableDirectory?.etag == metadata.etag && @@ -294,21 +312,16 @@ class NCFiles: NCCollectionViewCommon { startGUIGetServerData() let options = NKRequestOptions(timeout: 180) - let resultsReadFolder = await NCNetworking.shared.readFolderAsync( - serverUrl: serverUrl, - account: account, - options: options - ) { task in - Task { - await NCNetworking.shared.networkingTasks.track(identifier: "\(self.serverUrl)_NCFiles", task: task) - } + let (account, metadataFolder, metadatas, error) = await NCNetworking.shared.readFolderAsync(serverUrl: serverUrl, + account: session.account, + options: options) { task in + self.dataSourceTask = task if self.dataSource.isEmpty() { self.collectionView.reloadData() } - } - guard resultsReadFolder.error == .success else { - return(nil, resultsReadFolder.error, reloadRequired) + guard error == .success else { + return (nil, error, false) } if let metadataFolder { @@ -316,71 +329,81 @@ class NCFiles: NCCollectionViewCommon { self.richWorkspaceText = metadataFolder.richWorkspace } - guard e2eEncrypted, - let metadatas = resultsReadFolder.metadatas, - NCPreferences().isEndToEndEnabled(account: account), + guard let metadataFolder, + isDirectoryE2EE, + NCKeychain().isEndToEndEnabled(account: account), await !NCNetworkingE2EE().isInUpload(account: account, serverUrl: serverUrl) else { - return(resultsReadFolder.metadatas, resultsReadFolder.error, reloadRequired) + return (metadatas, error, true) } - // - // E2EE section - // - + /// E2EE let lock = await self.database.getE2ETokenLockAsync(account: account, serverUrl: serverUrl) - let resultsE2eeGetMetadata = await NCNetworkingE2EE().getMetadata(fileId: ocId, e2eToken: lock?.e2eToken, account: account) + 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) + + 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 resultsE2eeGetMetadata.error == .success, - let e2eMetadata = resultsE2eeGetMetadata.e2eMetadata, - let version = resultsE2eeGetMetadata.version else { - if resultsE2eeGetMetadata.error.errorCode == NCGlobal.shared.errorResourceNotFound { + 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 { - await showErrorBanner(windowScene: windowScene, - text: error.errorDescription, + await showErrorBanner(controller: self.controller, + errorDescription: error.errorDescription, errorCode: error.errorCode) } } else { - await showErrorBanner(windowScene: windowScene, - text: resultsE2eeGetMetadata.error.errorDescription, - errorCode: resultsE2eeGetMetadata.error.errorCode) + // show error + Task {@MainActor in + await showErrorBanner(controller: self.controller, + errorDescription: error.errorDescription, + errorCode: error.errorCode) + } } - return(metadatas, resultsE2eeGetMetadata.error, reloadRequired) + + return(metadatas, error, reloadRequired) } - var error = await NCEndToEndMetadata().decodeMetadata(e2eMetadata, - signature: resultsE2eeGetMetadata.signature, - serverUrl: serverUrl, session: self.session) + let errorDecodeMetadata = await NCEndToEndMetadata().decodeMetadata(e2eMetadata, signature: results.signature, serverUrl: serverUrl, session: self.session) + nkLog(debug: "Decode e2ee metadata with error: \(errorDecodeMetadata.errorCode)") - if error == .success { + if errorDecodeMetadata == .success { let capabilities = await NKCapabilities.shared.getCapabilities(for: self.session.account) - if version == "v1", capabilities.e2EEApiVersion.hasPrefix("2.") { - await showInfoBanner(windowScene: windowScene, text: "Conversion metadata v1 to v2 required, please wait...") + 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() - error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, updateVersionV1V2: true, account: account) + let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, updateVersionV1V2: true, account: account) if error != .success { - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + Task {@MainActor in + await showErrorBanner(controller: self.controller, + errorDescription: error.errorDescription, + errorCode: error.errorCode) + } } NCActivityIndicator.shared.stop() } } else { // Client Diagnostic await self.database.addDiagnosticAsync(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors) - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) - } - - // Error: Go back - if error != .success { - navigationController?.popViewController(animated: false) + Task {@MainActor in + await showErrorBanner(controller: self.controller, + errorDescription: error.errorDescription, + 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) { @@ -398,13 +421,17 @@ class NCFiles: NCCollectionViewCommon { } } - func open(metadata: tableMetadata?) async { - guard let metadata else { - return + 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 + if let indexPath = indexPath { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.collectionView(self.collectionView, didSelectItemAt: indexPath) + } + } } - await didSelectMetadata(metadata, withOcIds: false) } - + override func resetPlusButtonAlpha(animated: Bool = true) { accumulatedScrollDown = 0 let update = { @@ -441,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 @@ -459,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/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/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index 51ae38a961..83745fc15d 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -32,6 +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?) } extension NCSectionFirstHeaderDelegate { @@ -73,19 +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 = "" - -#if !EXTENSION - @MainActor - internal var controller: NCMainTabBarController? { - viewController?.tabBarController as? NCMainTabBarController - } -#endif override func awakeFromNib() { super.awakeFromNib() @@ -212,7 +204,6 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat heightHeaderSection: CGFloat, sectionText: String?, viewController: UIViewController?, - sceneItentifier: String, delegate: NCSectionFirstHeaderDelegate?) { viewRichWorkspaceHeightConstraint.constant = heightHeaderRichWorkspace viewRecommendationsHeightConstraint.constant = heightHeaderRecommendations @@ -226,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 { @@ -258,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) @@ -278,16 +271,7 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat // viewSection.isHidden = false // } -#if EXTENSION self.collectionViewRecommendations.reloadData() -#else - Task { - let isPause = await (viewController as? NCCollectionViewCommon)?.debouncerReloadDataSource.isPausedNow() ?? false - if !isPause { - self.collectionViewRecommendations.reloadData() - } - } -#endif } // MARK: - RichWorkspace @@ -333,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) @@ -345,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.metadata?.fileId == 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 } } } @@ -370,18 +351,19 @@ 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) } - cell.setBidiSafeFilename(metadata.fileNameView, isDirectory: metadata.directory, titleLabel: cell.labelFilename, extensionLabel: cell.labelExtensionFilename) + cell.labelFilename.text = metadata.fileNameView cell.labelInfo.text = recommendedFiles.reason cell.delegate = self cell.metadata = metadata cell.recommendedFiles = recommendedFiles + cell.id = recommendedFiles.id } return cell @@ -401,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 } @@ -412,10 +394,10 @@ 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 = NCContextMenuMain(metadata: metadata.detachedCopy(), viewController: viewController, controller: self.controller, sender: cell) + let contextMenu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: viewController, sceneIdentifier: self.sceneIdentifier, image: image, sender: cell) return contextMenu.viewMenu() }) #endif @@ -431,23 +413,7 @@ extension NCSectionFirstHeader: UICollectionViewDelegateFlowLayout { } extension NCSectionFirstHeader: NCRecommendationsCellDelegate { - func openContextMenu(with metadata: tableMetadata?, button: UIButton, sender: Any) { -#if !EXTENSION - Task { - guard let viewController = self.viewController, let metadata else { - return - } - button.menu = NCContextMenuMain(metadata: metadata, viewController: viewController, controller: self.controller, sender: sender).viewMenu() - } -#endif - } - - func onMenuIntent(with metadata: tableMetadata?) { -#if !EXTENSION - Task { - let collectionViewCommon = (self.viewController as? NCCollectionViewCommon) - await collectionViewCommon?.debouncerReloadData.pause() - } -#endif + 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 35e11b17177e9eb32a8919511c646f68067cf048 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 16 Dec 2025 17:32:06 +0530 Subject: [PATCH 4/5] NMC 2172 - Dashboard theming customisation changes --- iOSClient/Favorites/NCFavorite.swift | 1 - iOSClient/Files/NCFiles.swift | 409 ++++++++++-------- .../Cell/NCCellProtocol.swift | 23 +- ...ionViewCommon+CollectionViewDelegate.swift | 13 +- .../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 +- 10 files changed, 678 insertions(+), 768 deletions(-) diff --git a/iOSClient/Favorites/NCFavorite.swift b/iOSClient/Favorites/NCFavorite.swift index 86922bc700..4a020f53af 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/NCCollectionViewCommon+CollectionViewDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift index 4a57032ba3..d89eef4a4e 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift @@ -143,10 +143,15 @@ 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) 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 From a5bd861b296ddb8336e7db2c0b673004827488fb Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Thu, 9 Apr 2026 18:45:56 +0530 Subject: [PATCH 5/5] NMC 2172 - Dashboard theming customisation updated changes with 10.3.0 version --- iOSClient/Files/NCFiles.swift | 187 ++------- .../Collection Common/Cell/NCGridCell.swift | 22 + .../Collection Common/Cell/NCPhotoCell.swift | 35 ++ .../NCCollectionViewCommon+SelectTabBar.swift | 381 ++++++++++++++++++ .../NCCollectionViewCommonSelectTabBar.swift | 12 + .../NCCollectionViewDataSource.swift | 10 +- .../NCSelectableNavigationView.swift | 130 ++++++ .../NCSectionHeaderMenu.swift | 329 +++++++++++++++ .../NCSectionHeaderMenu.xib | 219 ++++++++++ iOSClient/Menu/NCMenuAction.swift | 124 +----- iOSClient/Offline/NCOffline.swift | 1 + 11 files changed, 1191 insertions(+), 259 deletions(-) create mode 100644 iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBar.swift create mode 100644 iOSClient/Main/Collection Common/NCSelectableNavigationView.swift create mode 100644 iOSClient/Main/Collection Common/Section Header Footer/NCSectionHeaderMenu.swift create mode 100644 iOSClient/Main/Collection Common/Section Header Footer/NCSectionHeaderMenu.xib diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index c33165f0c9..97d8cad64d 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -8,6 +8,7 @@ import RealmSwift import SwiftUI class NCFiles: NCCollectionViewCommon { + internal var fileNameBlink: String? internal var fileNameOpen: String? @@ -15,8 +16,6 @@ class NCFiles: NCCollectionViewCommon { internal var lastScrollTime: TimeInterval = 0 internal var accumulatedScrollDown: CGFloat = 0 - internal var syncMetadatasTask: Task? - required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) @@ -101,9 +100,6 @@ class NCFiles: NCCollectionViewCommon { super.viewWillAppear(animated) Task { - let capabilities = await database.getCapabilities(account: self.session.account) ?? NKCapabilities.Capabilities() - await mainNavigationController?.createPlusMenu(session: self.session, capabilities: capabilities) - await self.reloadDataSource() } } @@ -111,6 +107,12 @@ class NCFiles: NCCollectionViewCommon { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + // Re-evaluate in-app messages after viewDidAppear + MoEngageAnalytics.shared.displayInAppNotificationSafely(reason: "viewDidAppear") + + // Preload hidden NCMedia so it's ready without being shown +// NCMediaPreloader.shared.preloadIfNeeded() + if !self.dataSource.isEmpty() { self.blinkCell(fileName: self.fileNameBlink) self.openFile(fileName: self.fileNameOpen) @@ -147,53 +149,10 @@ class NCFiles: NCCollectionViewCommon { // MARK: - DataSource - override func reloadDataSource() { - guard !isSearchingMode - else { - return super.reloadDataSource() - } - - // 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() - } - - 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 - } - - 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) - - 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 { - await super.getServerData() - - defer { - stopGUIGetServerData() - startSyncMetadata(metadatas: self.dataSource.getMetadatas()) - } - - Task { - await networking.networkingTasks.cancel(identifier: "\(self.serverUrl)_NCFiles") + override func reloadDataSource() async { + guard !isSearchingMode else { + await super.reloadDataSource() + return } self.metadataFolder = await self.database.getMetadataFolderAsync(session: self.session, serverUrl: self.serverUrl) @@ -229,7 +188,7 @@ class NCFiles: NCCollectionViewCommon { override func getServerData(forced: Bool = false) async { defer { - restoreDefaultTitle() + stopGUIGetServerData() startSyncMetadata(metadatas: self.dataSource.getMetadatas()) } @@ -288,22 +247,30 @@ class NCFiles: NCCollectionViewCommon { self.collectionView.reloadData() } } - guard resultsReadFile.error == .success, let metadata = resultsReadFile.metadata else { - return (nil, resultsReadFile.error, false) + guard resultsReadFile.error == .success, + let metadata = resultsReadFile.metadata else { + return(nil, resultsReadFile.error, reloadRequired) } + 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 = ( - !refresh && + !forced && tableDirectory?.etag == metadata.etag && !metadata.e2eEncrypted && !self.dataSource.isEmpty() ) if shouldSkipUpdate { - return (nil, NKError(), false) + return (nil, NKError(), reloadRequired) } startGUIGetServerData() @@ -312,30 +279,40 @@ class NCFiles: NCCollectionViewCommon { let (account, metadataFolder, metadatas, error) = await NCNetworking.shared.readFolderAsync(serverUrl: serverUrl, account: session.account, options: options) { task in - self.dataSourceTask = task + Task { + await NCNetworking.shared.networkingTasks.track(identifier: "\(self.serverUrl)_NCFiles", task: task) + } if self.dataSource.isEmpty() { self.collectionView.reloadData() } + } guard error == .success else { - return (nil, error, false) + return(nil, error, reloadRequired) } + reloadRequired = true if let metadataFolder { self.metadataFolder = metadataFolder.detachedCopy() self.richWorkspaceText = metadataFolder.richWorkspace } - guard let metadataFolder, - isDirectoryE2EE, - NCKeychain().isEndToEndEnabled(account: account), + // + // E2EE section + // + + guard e2eEncrypted, + let metadatas, + !metadatas.isEmpty, + NCPreferences().isEndToEndEnabled(account: account), await !NCNetworkingE2EE().isInUpload(account: account, serverUrl: serverUrl) else { - return (metadatas, error, true) + return(metadatas, error, reloadRequired) } - /// E2EE let lock = await self.database.getE2ETokenLockAsync(account: account, serverUrl: serverUrl) - let results = await NCNetworkingE2EE().getMetadata(fileId: metadataFolder.ocId, e2eToken: lock?.e2eToken, account: account) + 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) @@ -397,79 +374,6 @@ class NCFiles: NCCollectionViewCommon { } } - 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) } @@ -518,12 +422,6 @@ class NCFiles: NCCollectionViewCommon { 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 } else if let account = tblAccount?.account, account != currentAccount { Task { @@ -539,3 +437,4 @@ class NCFiles: NCCollectionViewCommon { } } } + diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift index 57831aa19b..c4996f0c97 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift @@ -163,6 +163,12 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellMainP } func selected(_ status: Bool, isEditMode: Bool, color: UIColor) { + // E2EE - remove encrypt folder selection + if let metadata = NCManageDatabase.shared.getMetadataFromOcId(self.metadata?.ocId), metadata.e2eEncrypted { + imageSelect.isHidden = true + } else { + imageSelect.isHidden = isEditMode ? false : true + } if isEditMode { buttonMore.isHidden = true accessibilityCustomActions = nil @@ -173,6 +179,14 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellMainP imageVisualEffect.alpha = status ? 1 : 0 imageSelect.alpha = status ? 1 : 0 imageSelect.image = NCImageCache.shared.getImageCheckedYes(color: color) +// if status { +// imageSelect.image = NCImageCache.shared.getImageCheckedYes() +// imageVisualEffect.isHidden = false +// } else { +// imageSelect.image = NCImageCache.shared.getImageCheckedNo() +// backgroundView = nil +// imageVisualEffect.isHidden = true +// } } func writeInfoDateSize(date: NSDate, size: Int64) { @@ -189,6 +203,14 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellMainP accessibilityLabel = label accessibilityValue = value } + + func setIconOutlines() { + if imageStatus.image != nil { + imageStatus.makeCircularBackground(withColor: .systemBackground) + } else { + imageStatus.backgroundColor = .clear + } + } } // MARK: - Grid Layout diff --git a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift index 2e29d9690a..6d5a681752 100644 --- a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift @@ -45,10 +45,45 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellMain return nil } + @objc private func handleTapObserver(_ g: UITapGestureRecognizer) { + let location = g.location(in: contentView) + + if buttonMore.frame.contains(location) { + delegate?.onMenuIntent(with: metadata) + } + } + + func setButtonMore(image: UIImage) { + buttonMore.setImage(image, for: .normal) + } + + func hideButtonMore(_ status: Bool) { + // buttonMore.isHidden = status NO MORE USED + } + + func hideImageStatus(_ status: Bool) { + imageStatus.isHidden = status + } + func selected(_ status: Bool, isEditMode: Bool, color: UIColor) { imageVisualEffect.alpha = status ? 1 : 0 imageSelect.alpha = status ? 1 : 0 imageSelect.image = NCImageCache.shared.getImageCheckedYes(color: color) + // E2EE - remove encrypt folder selection + if let metadata = NCManageDatabase.shared.getMetadataFromOcId(self.metadata?.ocId), metadata.e2eEncrypted { + imageSelect.isHidden = true + } else { + imageSelect.isHidden = isEditMode ? false : true + } + if status { +// imageSelect.isHidden = false + imageVisualEffect.isHidden = false + imageSelect.image = NCImageCache.shared.getImageCheckedYes() + } else { +// imageSelect.isHidden = true + imageVisualEffect.isHidden = true + imageSelect.image = NCImageCache.shared.getImageCheckedNo() + } } func setAccessibility(label: String, value: String) { diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBar.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBar.swift new file mode 100644 index 0000000000..07dfe7c91f --- /dev/null +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBar.swift @@ -0,0 +1,381 @@ +// +// 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 +import NextcloudKit + +extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate, NCSelectableNavigationView { + + func selectAll() { + fileSelect = selectableDataSource.compactMap({ $0.primaryKeyValue }) + tabBarSelect?.update(fileSelect: fileSelect, metadatas: getSelectedMetadatas(), userId: session.userId) + DispatchQueue.main.async { + self.collectionView.reloadData() + self.setNavigationRightItems(enableMenu: false) + } + } + + func delete() { + var alertStyle = UIAlertController.Style.actionSheet + if UIDevice.current.userInterfaceIdiom == .pad { alertStyle = .alert } + let alertController = UIAlertController(title: NSLocalizedString("_confirm_delete_selected_", comment: ""), message: nil, preferredStyle: alertStyle) + let metadatas = getSelectedMetadatas() + let canDeleteServer = metadatas.allSatisfy { !$0.lock } + + if canDeleteServer { +// alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .destructive) { [self] _ in +// NCNetworking.shared.deleteMetadatas(metadatas, sceneIdentifier: self.controller?.sceneIdentifier) +// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) +// toggleSelect() + alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .destructive) { _ in + self.networking.setStatusWaitDelete(metadatas: metadatas, sceneIdentifier: self.controller?.sceneIdentifier) + self.setEditMode(false) + Task { + await self.reloadDataSource() + } + }) + } + + alertController.addAction(UIAlertAction(title: NSLocalizedString("_remove_local_file_", comment: ""), style: .destructive) { [self] (_: UIAlertAction) in + let copyMetadatas = metadatas + + Task { + var error = NKError() + var ocId: [String] = [] + for metadata in copyMetadatas where error == .success { + error = await NCNetworking.shared.deleteCache(metadata, sceneIdentifier: self.controller?.sceneIdentifier) + if error == .success { + ocId.append(metadata.ocId) + } + error = await self.networking.deleteCache(metadata, sceneIdentifier: self.controller?.sceneIdentifier) + } + NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterDeleteFile, userInfo: ["ocId": ocId, "error": error]) + } + toggleSelect() + }) + + alertController.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel) { (_: UIAlertAction) in }) + self.present(alertController, animated: true, completion: nil) + } + + func move() { + let metadatas = getSelectedMetadatas() + + NCActionCenter.shared.openSelectView(items: metadatas, controller: self.controller) + toggleSelect() + NCDownloadAction.shared.openSelectView(items: metadatas, controller: self.controller) + setEditMode(false) +// NCActionCenter.shared.openSelectView(items: metadatas, controller: self.controller) +// toggleSelect() + } + + func share() { + let metadatas = getSelectedMetadatas() + NCActionCenter.shared.openActivityViewController(selectedMetadata: metadatas, controller: self.controller) + toggleSelect() + NCDownloadAction.shared.openActivityViewController(selectedMetadata: metadatas, controller: self.controller, sender: nil) + setEditMode(false) +// NCActionCenter.shared.openActivityViewController(selectedMetadata: metadatas, controller: self.controller) +// toggleSelect() + } + + func saveAsAvailableOffline(isAnyOffline: Bool) { + let metadatas = getSelectedMetadatas() + if !isAnyOffline, metadatas.count > 3 { + let alert = UIAlertController( + title: NSLocalizedString("_set_available_offline_", comment: ""), + message: NSLocalizedString("_select_offline_warning_", comment: ""), + preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("_continue_", comment: ""), style: .default, handler: { _ in + Task { + for metadata in metadatas { + await NCDownloadAction.shared.setMetadataAvalableOffline(metadata, isOffline: isAnyOffline) + } + } + self.setEditMode(false) + + })) + alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel)) + self.present(alert, animated: true) + } else { +// metadatas.forEach { NCActionCenter.shared.setMetadataAvalableOffline($0, isOffline: isAnyOffline) } +// toggleSelect() + Task { + for metadata in metadatas { + await NCDownloadAction.shared.setMetadataAvalableOffline(metadata, isOffline: isAnyOffline) + } + } + setEditMode(false) + + } + } + + func lock(isAnyLocked: Bool) { + let metadatas = getSelectedMetadatas() + for metadata in metadatas where metadata.lock == isAnyLocked { + self.networking.lockUnlockFile(metadata, shoulLock: !isAnyLocked) + } + toggleSelect() + } + + func getSelectedMetadatas() -> [tableMetadata] { + var selectedMetadatas: [tableMetadata] = [] + for ocId in fileSelect { + guard let metadata = database.getMetadataFromOcId(ocId) else { continue } + selectedMetadatas.append(metadata) + } + return selectedMetadatas + } + + func setEditMode(_ editMode: Bool) { + isEditMode = editMode + fileSelect.removeAll() + + navigationItem.hidesBackButton = editMode + navigationController?.interactivePopGestureRecognizer?.isEnabled = !editMode + searchController(enabled: !editMode) + + if editMode { + navigationItem.leftBarButtonItems = nil + } else { + ///Magentacloud branding changes hide user account button on left navigation bar + setNavigationLeftItems() + } + + navigationController?.interactivePopGestureRecognizer?.isEnabled = !editMode + navigationItem.hidesBackButton = editMode + searchController(enabled: !editMode) + self.setNavigationRightItems(enableMenu: true) + 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() + self.setEditMode(self.isEditMode) + } + } + + 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 } + if fileSelect.count != selectableDataSource.count { + actions.append(.selectAllAction(action: selectAll)) + } + + guard !fileSelect.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}) + for ocId in fileSelect { + guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { continue } + if metadata.e2eEncrypted { + fileSelect.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 != session.userId { + canUnlock = false + } + } + + guard !isAnyOffline else { continue } + if metadata.directory, + 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 + } // 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(.openInAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: { self.toggleSelect() })) + } + + 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() + 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 + } + + 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/NCCollectionViewCommonSelectTabBar.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommonSelectTabBar.swift index 5ae9be568e..3ce153429c 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommonSelectTabBar.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommonSelectTabBar.swift @@ -195,6 +195,18 @@ struct NCCollectionViewCommonSelectTabBarView: View { tabBarSelect.delegate?.lock(isAnyLocked: tabBarSelect.isAnyLocked) }, label: { Label(NSLocalizedString(tabBarSelect.isAnyLocked ? "_unlock_" : "_lock_", comment: ""), systemImage: tabBarSelect.isAnyLocked ? "lock.open" : "lock") + // NMC-5295 - iOS v10.2.2.3 - File Browser: Remove "file lock" feature from menu + // lock menu entry is not available. not supported by magentacloud +// Button(action: { +// tabBarSelect.delegate?.lock(isAnyLocked: tabBarSelect.isAnyLocked) +// }, label: { +// Label(NSLocalizedString(tabBarSelect.isAnyLocked ? "_unlock_" : "_lock_", comment: ""), systemImage: tabBarSelect.isAnyLocked ? "lock.open" : "lock") +// +// if !tabBarSelect.enableLock { +// Text(NSLocalizedString("_lock_no_permissions_selected_", comment: "")) +// } +// }) +// .disabled(!tabBarSelect.enableLock || tabBarSelect.isSelectedEmpty) if !tabBarSelect.enableLock { Text(NSLocalizedString("_lock_no_permissions_selected_", comment: "")) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewDataSource.swift index e937164631..d42c184c85 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewDataSource.swift @@ -234,10 +234,16 @@ class NCCollectionViewDataSource: NSObject { } func getFooterInformation() -> (directories: Int, files: Int, size: Int64) { - let directories = metadatas.filter({ $0.directory == true}) - let files = metadatas.filter({ $0.directory == false}) + let validMetadatas = metadatas.filter { !$0.isInvalidated } + let directories = validMetadatas.filter({ $0.directory == true}) + let files = validMetadatas.filter({ $0.directory == false}) + var size: Int64 = 0 + directories.forEach { metadata in + size += metadata.size + } + files.forEach { metadata in size += metadata.size } diff --git a/iOSClient/Main/Collection Common/NCSelectableNavigationView.swift b/iOSClient/Main/Collection Common/NCSelectableNavigationView.swift new file mode 100644 index 0000000000..17dbb04c03 --- /dev/null +++ b/iOSClient/Main/Collection Common/NCSelectableNavigationView.swift @@ -0,0 +1,130 @@ +// +// NCSelectableNavigationView.swift +// Nextcloud +// +// Created by Henrik Storch on 27.01.22. +// Copyright © 2022 Henrik Storch. All rights reserved. +// +// Author Marino Faggiana +// Author Henrik Storch +// +// 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 NextcloudKit +import Realm +import UIKit + +extension RealmSwiftObject { + var primaryKeyValue: String? { + guard let primaryKeyName = self.objectSchema.primaryKeyProperty?.name else { return nil } + return value(forKey: primaryKeyName) as? String + } +} + +public protocol NCSelectableViewTabBar { + var tabBarController: UITabBarController? { get } + var hostingController: UIViewController? { get } +} + +protocol NCSelectableNavigationView: AnyObject { + var viewController: UIViewController { get } +// var appDelegate: AppDelegate { get } + var selectableDataSource: [RealmSwiftObject] { get } + var collectionView: UICollectionView! { get set } + var isEditMode: Bool { get set } + var fileSelect: [String] { get set } +// var selectIndexPaths: [IndexPath] { get set } + var appDelegate: AppDelegate { get } + var selectableDataSource: [RealmSwiftObject] { get } + var collectionView: UICollectionView! { get set } + var isEditMode: Bool { get set } + var selectOcId: [String] { get set } + var appDelegate: AppDelegate { get } + var selectIndexPaths: [IndexPath] { get set } + var titleCurrentFolder: String { get } + var navigationItem: UINavigationItem { get } + var navigationController: UINavigationController? { get } + var layoutKey: String { get } + var serverUrl: String { get } +// var tabBarSelect: NCSelectableViewTabBar? { get set } +// var dataSource: NCCollectionViewDataSource { get set } + +// func reloadDataSource(withQueryDB: Bool) + var tabBarSelect: NCSelectableViewTabBar? { get set } + + func reloadDataSource(withQueryDB: Bool) +// func reloadDataSource(withQueryDB: Bool) + func setNavigationLeftItems() + func setNavigationRightItems(enableMenu: Bool) + func createMenuActions() -> [NCMenuAction] + + func toggleSelect(isOn: Bool?) + func onListSelected() + func onGridSelected() +} + +extension NCSelectableNavigationView { + func setNavigationLeftItems() {} + + func saveLayout(_ layoutForView: NCDBLayoutForView) { + NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView) + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) + setNavigationRightItems(enableMenu: 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.fileSelect.removeAll() +// self.selectIndexPaths.removeAll() + self.selectOcId.removeAll() + self.selectIndexPaths.removeAll() + self.setNavigationLeftItems() + self.setNavigationRightItems(enableMenu: true) + self.collectionView.reloadData() + } + } + + func collectionViewSelectAll() { + + fileSelect = selectableDataSource.compactMap({ $0.primaryKeyValue }) +// fileSelect = NCCollectionViewDataSource().getMetadataSourceForAllSections().compactMap({ $0.primaryKeyValue }) +// selectOcId = selectableDataSource.compactMap({ $0.primaryKeyValue }) + collectionView.reloadData() + setNavigationRightItems(enableMenu: false) + } + + func tapNotification() { + if let viewController = UIStoryboard(name: "NCNotification", bundle: nil).instantiateInitialViewController() as? NCNotification { + navigationController?.pushViewController(viewController, animated: true) + } + } + + func tapTransfer() { + if let navController = UIStoryboard(name: "NCTransfers", bundle: nil).instantiateInitialViewController() as? UINavigationController, + let viewController = navController.topViewController as? NCTransfers { + viewController.modalPresentationStyle = .pageSheet +// self.present(navigationController, animated: true, completion: nil) + navigationController?.present(navController, animated: true, completion: nil) + } + } +} + +extension NCSelectableNavigationView where Self: UIViewController { + var viewController: UIViewController { + self + } +} diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionHeaderMenu.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionHeaderMenu.swift new file mode 100644 index 0000000000..6cd8ae727d --- /dev/null +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionHeaderMenu.swift @@ -0,0 +1,329 @@ +// +// NCSectionHeaderFooter.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 + +class NCSectionHeaderMenu: 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 viewButtonsView: UIView! + @IBOutlet weak var viewSeparator: UIView! + @IBOutlet weak var viewRichWorkspace: UIView! + @IBOutlet weak var viewSection: UIView! + + @IBOutlet weak var viewTransferHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var viewButtonsViewHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var viewSeparatorHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var viewRichWorkspaceHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var viewSectionHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var transferSeparatorBottomHeightConstraint: NSLayoutConstraint! + + weak var delegate: NCSectionHeaderMenuDelegate? + let utility = NCUtility() + private var markdownParser = MarkdownParser() + private var richWorkspaceText: String? + private var textViewColor: UIColor? + private let gradient: CAGradientLayer = CAGradientLayer() + + override func awakeFromNib() { + super.awakeFromNib() + + backgroundColor = .clear + + buttonSwitch.setImage(UIImage(systemName: "list.bullet")!.image(color: NCBrandColor.shared.iconImageColor, size: 25), for: .normal) + + buttonOrder.setTitle("", for: .normal) + buttonOrder.setTitleColor(NCBrandColor.shared.brand, for: .normal) + buttonMore.setImage(UIImage(named: "more")!.image(color: NCBrandColor.shared.iconImageColor, size: 25), for: .normal) + + // Gradient + gradient.startPoint = CGPoint(x: 0, y: 0.50) + gradient.endPoint = CGPoint(x: 0, y: 1) + 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: .label) + markdownParser.header.font = UIFont.systemFont(ofSize: 25) + if let richWorkspaceText = richWorkspaceText { + textViewRichWorkspace.attributedText = markdownParser.parse(richWorkspaceText) + } + textViewColor = .label + + 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) { + super.layoutSublayers(of: layer) + + gradient.frame = viewRichWorkspace.bounds + setInterfaceColor() + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + setInterfaceColor() + } + + // 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.iconImageColor, 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 setRichWorkspaceHeight(_ size: CGFloat) { + + viewRichWorkspaceHeightConstraint.constant = size + if size == 0 { + viewRichWorkspace.isHidden = true + } else { + viewRichWorkspace.isHidden = false + } + } + + func setInterfaceColor() { + + if traitCollection.userInterfaceStyle == .dark { + gradient.colors = [UIColor(white: 0, alpha: 0).cgColor, UIColor.black.cgColor] + } else { + gradient.colors = [UIColor(white: 1, alpha: 0).cgColor, UIColor.white.cgColor] + } + } + + func setRichWorkspaceText(_ text: String?) { + guard let text = text else { return } + + if text != self.richWorkspaceText { + textViewRichWorkspace.attributedText = markdownParser.parse(text) + self.richWorkspaceText = text + } + } + + // 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 { + var image: UIImage? + if let ocId, + let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) { + image = utility.createFilePreviewImage(ocId: metadata.ocId, etag: metadata.etag, fileNameView: metadata.fileNameView, classFile: metadata.classFile, status: metadata.status, createPreviewMedia: true)//?.darken() + if image == nil { + image = UIImage(named: metadata.iconName) + buttonTransfer.backgroundColor = .lightGray + } else { + buttonTransfer.backgroundColor = .clear + } + buttonTransfer.setImage(image, for: .normal) + } + viewTransferHeightConstraint.constant = NCGlobal.shared.heightHeaderTransfer + if let progress { + progressTransfer.progress = progress + } + } + } + + // MARK: - Section + + func setSectionHeight(_ size: CGFloat) { + + viewSectionHeightConstraint.constant = size + if size == 0 { + viewSection.isHidden = true + } else { + viewSection.isHidden = false + } + } + + // 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) + } +} + +protocol NCSectionHeaderMenuDelegate: AnyObject { + func tapButtonSwitch(_ sender: Any) + func tapButtonOrder(_ sender: Any) + func tapButtonMore(_ sender: Any) + func tapButtonTransfer(_ sender: Any) + func tapRichWorkspace(_ sender: Any) +} + +// optional func +extension NCSectionHeaderMenuDelegate { + func tapButtonSwitch(_ sender: Any) {} + func tapButtonOrder(_ sender: Any) {} + func tapButtonMore(_ sender: Any) {} + func tapButtonTransfer(_ sender: Any) {} + func tapRichWorkspace(_ sender: Any) {} +} + +// optional func +extension NCSectionFooterDelegate { + func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) {} +} + +//// https://stackoverflow.com/questions/16278463/darken-an-uiimage +//public extension UIImage { +// +// private enum BlendMode { +// case multiply // This results in colors that are at least as dark as either of the two contributing sample colors +// case screen // This results in colors that are at least as light as either of the two contributing sample colors +// } +// +// // A level of zero yeilds the original image, a level of 1 results in black +// func darken(level: CGFloat = 0.5) -> UIImage? { +// return blend(mode: .multiply, level: level) +// } +// +// // A level of zero yeilds the original image, a level of 1 results in white +// func lighten(level: CGFloat = 0.5) -> UIImage? { +// return blend(mode: .screen, level: level) +// } +// +// private func blend(mode: BlendMode, level: CGFloat) -> UIImage? { +// let context = CIContext(options: nil) +// +// var level = level +// if level < 0 { +// level = 0 +// } else if level > 1 { +// level = 1 +// } +// +// let filterName: String +// switch mode { +// case .multiply: // As the level increases we get less white +// level = abs(level - 1.0) +// filterName = "CIMultiplyBlendMode" +// case .screen: // As the level increases we get more white +// filterName = "CIScreenBlendMode" +// } +// +// let blender = CIFilter(name: filterName)! +// let backgroundColor = CIColor(color: UIColor(white: level, alpha: 1)) +// +// guard let inputImage = CIImage(image: self) else { return nil } +// blender.setValue(inputImage, forKey: kCIInputImageKey) +// +// guard let backgroundImageGenerator = CIFilter(name: "CIConstantColorGenerator") else { return nil } +// backgroundImageGenerator.setValue(backgroundColor, forKey: kCIInputColorKey) +// guard let backgroundImage = backgroundImageGenerator.outputImage?.cropped(to: CGRect(origin: CGPoint.zero, size: self.size)) else { return nil } +// blender.setValue(backgroundImage, forKey: kCIInputBackgroundImageKey) +// +// guard let blendedImage = blender.outputImage else { return nil } +// +// guard let cgImage = context.createCGImage(blendedImage, from: blendedImage.extent) else { return nil } +// return UIImage(cgImage: cgImage) +// } +//} diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionHeaderMenu.xib b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionHeaderMenu.xib new file mode 100644 index 0000000000..5d206c1d2f --- /dev/null +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionHeaderMenu.xib @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Menu/NCMenuAction.swift b/iOSClient/Menu/NCMenuAction.swift index 1391db46f5..f623c4b139 100644 --- a/iOSClient/Menu/NCMenuAction.swift +++ b/iOSClient/Menu/NCMenuAction.swift @@ -25,6 +25,7 @@ import Foundation import UIKit import NextcloudKit +import SwiftUI @available(*, deprecated, message: "Change to using iOS native context menus, as well as using ContextMenuActions and NCViewerContextMenu") class NCMenuAction { @@ -90,7 +91,7 @@ extension NCMenuAction { static func selectAllAction(sender: Any?, action: @escaping () -> Void) -> NCMenuAction { NCMenuAction( title: NSLocalizedString("_select_all_", comment: ""), - icon: NCUtility().loadImage(named: "checkmark.circle.fill", colors: [NCBrandColor.shared.iconImageColor]), + icon: NCUtility().loadImage(named: "checkmark.circle.fill", colors: [NCBrandColor.shared.iconImageColor]).withTintColor(NCBrandColor.shared.iconImageColor), sender: sender, action: { _ in action() } ) @@ -100,7 +101,7 @@ extension NCMenuAction { static func cancelAction(sender: Any?, action: @escaping () -> Void) -> NCMenuAction { NCMenuAction( title: NSLocalizedString("_cancel_", comment: ""), - icon: NCUtility().loadImage(named: "xmark", colors: [NCBrandColor.shared.iconImageColor]), + icon: NCUtility().loadImage(named: "xmark", colors: [NCBrandColor.shared.iconImageColor]).withTintColor(NCBrandColor.shared.iconImageColor), sender: sender, action: { _ in action() } ) @@ -151,11 +152,11 @@ extension NCMenuAction { return NCMenuAction( title: titleDelete, destructive: destructive, - icon: NCUtility().loadImage(named: icon, colors: [color]), + icon: NCUtility().loadImage(named: icon, colors: [color]).withTintColor(NCBrandColor.shared.iconImageColor), order: order, sender: sender, action: { _ in - let alertController = UIAlertController.deleteFileOrFolder(titleString: titleDelete + "?", message: message + fileList, canDeleteServer: canDeleteServer, selectedMetadatas: selectedMetadatas, sceneIdentifier: controller?.sceneIdentifier) { _ in + let alertController = UIAlertController.deleteFileOrFolder(titleString: titleDelete + "?", message: message + fileList, canDeleteServer: canDeleteServer, selectedMetadatas: selectedMetadatas, sceneIdentifier: controller?.sceneIdentifier, controller: controller) { _ in completion?() } @@ -169,7 +170,7 @@ extension NCMenuAction { // 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]), + icon: NCUtility().loadImage(named: "open_file",colors: [NCBrandColor.shared.iconImageColor]).withTintColor(NCBrandColor.shared.iconImageColor), order: order, sender: sender, action: { _ in @@ -185,7 +186,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: "cloudDownload", colors: [NCBrandColor.shared.iconImageColor]), + icon: NCUtility().loadImage(named: "cloudDownload", colors: [NCBrandColor.shared.iconImageColor]).withTintColor(NCBrandColor.shared.iconImageColor), order: order, sender: sender, action: { _ in @@ -222,11 +223,11 @@ extension 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]), + icon: NCUtility().loadImage(named: "copy", colors: [NCBrandColor.shared.iconColor]), order: order, sender: sender, action: { _ in - NCDownloadAction.shared.copyPasteboard(pasteboardOcIds: fileSelect, controller: controller) +// NCActionCenter.shared.copyPasteboard(pasteboardOcIds: fileSelect, controller: controller) completion?() } ) @@ -236,7 +237,7 @@ extension NCMenuAction { 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: "move", colors: [NCBrandColor.shared.iconImageColor]), + icon: NCUtility().loadImage(named: "move", colors: [NCBrandColor.shared.iconImageColor]).withTintColor(NCBrandColor.shared.iconImageColor), order: order, sender: sender, action: { _ in @@ -278,7 +279,7 @@ extension NCMenuAction { let imageName = !shouldLock ? "lock_open" : "lock" return NCMenuAction( title: NSLocalizedString(titleKey, comment: ""), - icon: NCUtility().loadImage(named: imageName, colors: [NCBrandColor.shared.iconImageColor]), + icon: NCUtility().loadImage(named: imageName, colors: [NCBrandColor.shared.iconImageColor]).withTintColor(NCBrandColor.shared.iconImageColor), order: order, sender: sender, action: { _ in @@ -289,107 +290,4 @@ 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/Offline/NCOffline.swift b/iOSClient/Offline/NCOffline.swift index 2aca7501ca..6c2d512a6d 100644 --- a/iOSClient/Offline/NCOffline.swift +++ b/iOSClient/Offline/NCOffline.swift @@ -49,6 +49,7 @@ class NCOffline: NCCollectionViewCommon { Task { await getServerData() } + AnalyticsHelper.shared.trackEvent(eventName: .SCREEN_EVENT__OFFLINE_FILES) } // MARK: - DataSource