From a4ee59369495336700c96ef5852b66ca7cfa4736 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 8 Aug 2025 15:46:24 +0200 Subject: [PATCH 01/63] Add menu Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCCollectionViewCommon+Menu.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/iOSClient/Menu/NCCollectionViewCommon+Menu.swift b/iOSClient/Menu/NCCollectionViewCommon+Menu.swift index 94f4a81635..371425f93b 100644 --- a/iOSClient/Menu/NCCollectionViewCommon+Menu.swift +++ b/iOSClient/Menu/NCCollectionViewCommon+Menu.swift @@ -407,6 +407,20 @@ extension NCCollectionViewCommon { actions.append(.deleteOrUnshareAction(selectedMetadatas: [metadata], metadataFolder: metadataFolder, controller: self.controller, order: 170, sender: sender)) } + capabilities.declarativeUI?.contextMenu.forEach { item in + actions.append( + NCMenuAction( + title: item.title, + icon: utility.loadImage(named: "testtube.2", colors: [NCBrandColor.shared.presentationIconColor]), + order: Int.max, + sender: sender, + action: { _ in + + } + ) + ) + } + applicationHandle.addCollectionViewCommonMenu(metadata: metadata, image: image, actions: &actions) presentMenu(with: actions, controller: controller, sender: sender) From fd873373f0511bc57185fc5b8d6ea18f7b2c5eb0 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 22 Sep 2025 11:07:33 +0200 Subject: [PATCH 02/63] WIP Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 2 + .../Menu/NCCollectionViewCommon+Menu.swift | 60 +++++++++++++++---- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 839c6f716a..af60498d31 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -1375,6 +1375,7 @@ F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Extension.swift"; sourceTree = ""; }; F351D1A52D0AF24A00930F94 /* PHAssetCollection+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PHAssetCollection+Extension.swift"; sourceTree = ""; }; F359D8662A7D03420023F405 /* NCUtility+Exif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCUtility+Exif.swift"; sourceTree = ""; }; + F35FAC252E7C5FD70077994E /* NextcloudKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = NextcloudKit; path = ../NextcloudKit; sourceTree = SOURCE_ROOT; }; F36E64F62B9245210085ABB5 /* NCCollectionViewCommon+SelectTabBarDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+SelectTabBarDelegate.swift"; sourceTree = ""; }; F37208742BAB4AB0006B5430 /* TestConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; F3754A7C2CF87D600009312E /* SetupPasscodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupPasscodeView.swift; sourceTree = ""; }; @@ -3334,6 +3335,7 @@ C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */, C0046CDA2A17B98400D87C9D /* NextcloudUITests.xctest */, F7F1FBA62E27D13700C79E20 /* Frameworks */, + F35FAC252E7C5FD70077994E /* NextcloudKit */, ); sourceTree = ""; }; diff --git a/iOSClient/Menu/NCCollectionViewCommon+Menu.swift b/iOSClient/Menu/NCCollectionViewCommon+Menu.swift index 371425f93b..59b60d4018 100644 --- a/iOSClient/Menu/NCCollectionViewCommon+Menu.swift +++ b/iOSClient/Menu/NCCollectionViewCommon+Menu.swift @@ -29,6 +29,7 @@ import UIKit import FloatingPanel import NextcloudKit import Queuer +import SVGKit extension NCCollectionViewCommon { func toggleMenu(metadata: tableMetadata, image: UIImage?, sender: Any?) { @@ -407,19 +408,53 @@ extension NCCollectionViewCommon { actions.append(.deleteOrUnshareAction(selectedMetadatas: [metadata], metadataFolder: metadataFolder, controller: self.controller, order: 170, sender: sender)) } - capabilities.declarativeUI?.contextMenu.forEach { item in - actions.append( - NCMenuAction( - title: item.title, - icon: utility.loadImage(named: "testtube.2", colors: [NCBrandColor.shared.presentationIconColor]), - order: Int.max, - sender: sender, - action: { _ in + if let apps = capabilities.declarativeUI?.apps { + for (appName, context) in apps { + for item in context.contextMenu { - } - ) - ) - } + if item.mimetypeFilters == nil || (item.mimetypeFilters?.contains(metadata.contentType) == true) { + let iconImage: UIImage + if let icon = item.icon, let source = SVGKSourceURL.source(from: URL(string: icon)) { + iconImage = SVGKImage(source: source)?.uiImage ?? UIImage() + } else { + iconImage = utility.loadImage(named: "testtube.2", colors: [NCBrandColor.shared.presentationIconColor]) + } + + actions.append( + NCMenuAction( + title: item.name, + icon: iconImage, + order: Int.max, + sender: sender, + action: { _ in + Task { + await NextcloudKit.shared.sendRequestAsync(url: item.url, + method: item.method, + userAgent: userAgent, + params: item.params, + bodyParams: item.bodyParams) + } + } + ) + ) + } + } + } + } + +// capabilities.declarativeUI?.contextMenu.forEach { item in +// actions.append( +// NCMenuAction( +// title: item.title, +// icon: utility.loadImage(named: "testtube.2", colors: [NCBrandColor.shared.presentationIconColor]), +// order: Int.max, +// sender: sender, +// action: { _ in +// +// } +// ) +// ) +// } applicationHandle.addCollectionViewCommonMenu(metadata: metadata, image: image, actions: &actions) @@ -436,3 +471,4 @@ extension TimeInterval { return formatter.string(from: self) } } + From ed8a5270ae01f972a7dd58cb3fc874847b7ec91f Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 22 Sep 2025 13:19:56 +0200 Subject: [PATCH 03/63] WIP Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCCollectionViewCommon+Menu.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/iOSClient/Menu/NCCollectionViewCommon+Menu.swift b/iOSClient/Menu/NCCollectionViewCommon+Menu.swift index 59b60d4018..4a7ac0f133 100644 --- a/iOSClient/Menu/NCCollectionViewCommon+Menu.swift +++ b/iOSClient/Menu/NCCollectionViewCommon+Menu.swift @@ -411,10 +411,9 @@ extension NCCollectionViewCommon { if let apps = capabilities.declarativeUI?.apps { for (appName, context) in apps { for item in context.contextMenu { - if item.mimetypeFilters == nil || (item.mimetypeFilters?.contains(metadata.contentType) == true) { let iconImage: UIImage - if let icon = item.icon, let source = SVGKSourceURL.source(from: URL(string: icon)) { + if let iconUrl = item.icon, let source = SVGKSourceURL.source(from: URL(string: metadata.urlBase + iconUrl)) { iconImage = SVGKImage(source: source)?.uiImage ?? UIImage() } else { iconImage = utility.loadImage(named: "testtube.2", colors: [NCBrandColor.shared.presentationIconColor]) From 965977e99edea51e6322942c9ac2ad0157669861 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 23 Sep 2025 10:25:45 +0200 Subject: [PATCH 04/63] Replace sheet with native context menu Signed-off-by: Milen Pivchev --- .../Collection Common/Cell/NCListCell.xib | 2 +- ...ionViewCommon+CollectionViewDelegate.swift | 2 +- iOSClient/Menu/NCContextMenu.swift | 307 +++++++++++++++++- 3 files changed, 307 insertions(+), 4 deletions(-) diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.xib b/iOSClient/Main/Collection Common/Cell/NCListCell.xib index 980ca81d48..ac18f0bce6 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.xib @@ -59,7 +59,7 @@ - - - - - @@ -74,9 +55,8 @@ - - + From 476a9bbdf1d5aebb143fd6fa5fb0d07af765f71c Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 10 Dec 2025 15:41:13 +0100 Subject: [PATCH 18/63] WIP Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 4 - .../Collection Common/Cell/NCGridCell.swift | 5 - .../Collection Common/Cell/NCListCell.swift | 1 - .../Cell/NCRecommendationsCell.swift | 3 +- .../NCCollectionViewCommon.swift | 17 - .../NCSectionFirstHeader.swift | 8 - .../Menu/NCCollectionViewCommon+Menu.swift | 479 ------------------ iOSClient/Menu/NCContextMenu.swift | 136 +++-- 8 files changed, 61 insertions(+), 592 deletions(-) delete mode 100644 iOSClient/Menu/NCCollectionViewCommon+Menu.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 62f6f3c51a..a4c0348bb8 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -552,7 +552,6 @@ F77444F622281649000D5EB0 /* NCMediaCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F77444F422281649000D5EB0 /* NCMediaCell.xib */; }; F77444F8222816D5000D5EB0 /* NCPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77444F7222816D5000D5EB0 /* NCPickerViewController.swift */; }; F778231E2C42C07C001BB94F /* NCCollectionViewCommon+MediaLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = F778231D2C42C07C001BB94F /* NCCollectionViewCommon+MediaLayout.swift */; }; - F77A697D250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77A697C250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift */; }; F77B0F631D118A16002130FE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F7E70DE91A24DE4100E1B66A /* Localizable.strings */; }; F77B0F7D1D118A16002130FE /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F7F67BB81A24D27800EE80DA /* Images.xcassets */; }; F77BB746289984CA0090FC19 /* UIViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77BB745289984CA0090FC19 /* UIViewController+Extension.swift */; }; @@ -1510,7 +1509,6 @@ F77444F422281649000D5EB0 /* NCMediaCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCMediaCell.xib; sourceTree = ""; }; F77444F7222816D5000D5EB0 /* NCPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCPickerViewController.swift; sourceTree = ""; }; F778231D2C42C07C001BB94F /* NCCollectionViewCommon+MediaLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+MediaLayout.swift"; sourceTree = ""; }; - F77A697C250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+Menu.swift"; sourceTree = ""; }; F77BB745289984CA0090FC19 /* UIViewController+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extension.swift"; sourceTree = ""; }; F77BB747289985270090FC19 /* UITabBarController+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITabBarController+Extension.swift"; sourceTree = ""; }; F77BB7492899857B0090FC19 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = ""; }; @@ -2002,7 +2000,6 @@ isa = PBXGroup; children = ( F376A3732E5CC5FF0067EE25 /* ContextMenuActions.swift */, - F77A697C250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift */, F78C6FDD296D677300C952C3 /* NCContextMenu.swift */, 3704EB2923D5A58400455C5B /* NCMenu.storyboard */, 371B5A2D23D0B04500FAFAE9 /* NCMenu.swift */, @@ -4510,7 +4507,6 @@ F710D2022405826100A6033D /* NCViewerContextMenu.swift in Sources */, F765E9CD295C585800A09ED8 /* NCUploadScanDocument.swift in Sources */, F741C2242B6B9FD600E849BB /* NCMediaSelectTabBar.swift in Sources */, - F77A697D250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift in Sources */, F7BF9D822934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */, AA8D31662D411FA100FE2775 /* NCShareDateCell.swift in Sources */, F3F442EE2DDE292D00FD701F /* NCMetadataPermissions.swift in Sources */, diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift index 6d507dfd3a..cac0e82fed 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift @@ -129,10 +129,6 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto return nil } - @IBAction func touchUpInsideMore(_ sender: Any) { - gridCellDelegate?.tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: imageItem.image, sender: sender) - } - @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { gridCellDelegate?.longPressGridItem(with: ocId, ocIdTransfer: ocIdTransfer, gestureRecognizer: gestureRecognizer) } @@ -195,7 +191,6 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } protocol NCGridCellDelegate: AnyObject { - func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) } diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 55ae4ccf18..a4eae1b4b6 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -296,7 +296,6 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto protocol NCListCellDelegate: AnyObject { func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) - func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) } diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift index d7bd73233c..c4281d8d92 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift @@ -17,7 +17,6 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { @IBOutlet weak var labelFilename: UILabel! @IBOutlet weak var labelInfo: UILabel! - var delegate: NCRecommendationsCellDelegate? var metadata: tableMetadata = tableMetadata() var recommendedFiles: tableRecommendedFiles = tableRecommendedFiles() var id: String = "" @@ -33,6 +32,8 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { } func initCell() { + + image.image = nil labelFilename.text = "" labelInfo.text = "" diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index db762ea5b8..95a9b262a0 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -605,25 +605,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - TAP EVENT - func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { - tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) - } - - func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { - tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) - } - func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) { guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) } - func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { - guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } - toggleMenu(metadata: metadata, image: image, sender: sender) - } - func tapRichWorkspace(_ sender: Any) { if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { if let viewerRichWorkspace = navigationController.topViewController as? NCViewerRichWorkspace { @@ -637,10 +624,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } - func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { - toggleMenu(metadata: metadata, image: image, sender: sender) - } - func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { unifiedSearchMore(metadataForSection: metadataForSection) } diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index 3a3f982288..63c79ef815 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -9,7 +9,6 @@ import NextcloudKit protocol NCSectionFirstHeaderDelegate: AnyObject { func tapRichWorkspace(_ sender: Any) func tapRecommendations(with metadata: tableMetadata) - func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) } class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegate { @@ -205,7 +204,6 @@ extension NCSectionFirstHeader: UICollectionViewDataSource { cell.labelFilename.text = metadata.fileNameView cell.labelInfo.text = recommendedFiles.reason - cell.delegate = self cell.metadata = metadata cell.recommendedFiles = recommendedFiles cell.id = recommendedFiles.id @@ -256,9 +254,3 @@ extension NCSectionFirstHeader: UICollectionViewDelegateFlowLayout { return CGSize(width: cellHeight, height: cellHeight) } } - -extension NCSectionFirstHeader: NCRecommendationsCellDelegate { - func touchUpInsideButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { - self.delegate?.tapRecommendationsButtonMenu(with: metadata, image: image, sender: sender) - } -} diff --git a/iOSClient/Menu/NCCollectionViewCommon+Menu.swift b/iOSClient/Menu/NCCollectionViewCommon+Menu.swift deleted file mode 100644 index 634ee1cfdf..0000000000 --- a/iOSClient/Menu/NCCollectionViewCommon+Menu.swift +++ /dev/null @@ -1,479 +0,0 @@ -// -// NCCollectionViewCommon+Menu.swift -// Nextcloud -// -// Created by Philippe Weidmann on 24.01.20. -// Copyright © 2020 Philippe Weidmann. All rights reserved. -// Copyright © 2020 Marino Faggiana All rights reserved. -// Copyright © 2022 Henrik Storch. All rights reserved. -// -// Author Philippe Weidmann -// 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 UIKit -import FloatingPanel -import NextcloudKit -import Queuer -import SVGKit - -extension NCCollectionViewCommon { - func toggleMenu(metadata: tableMetadata, image: UIImage?, sender: Any?) { - guard let metadata = database.getMetadataFromOcId(metadata.ocId), - let sceneIdentifier = self.controller?.sceneIdentifier else { - return - } - let tableLocalFile = database.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) - let fileExists = NCUtilityFileSystem().fileProviderStorageExists(metadata) - var actions = [NCMenuAction]() - var isOffline: Bool = false - let applicationHandle = NCApplicationHandle() - var iconHeader: UIImage! - let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() - - if metadata.directory, let directory = database.getTableDirectory(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) { - isOffline = directory.offline - } else if let localFile = database.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) { - isOffline = localFile.offline - } - - if let image { - iconHeader = image - } else { - if metadata.directory { - iconHeader = imageCache.getFolder(account: metadata.account) - } else { - iconHeader = imageCache.getImageFile() - } - } - - actions.append( - NCMenuAction( - title: metadata.fileNameView, - boldTitle: true, - icon: iconHeader, - order: 0, - sender: sender, - action: nil - ) - ) - - actions.append(.seperator(order: 1, sender: sender)) - - // - // DETAILS - // - if NCNetworking.shared.isOnline, - !(!capabilities.fileSharingApiEnabled && !capabilities.filesComments && capabilities.activity.isEmpty) { - actions.append( - NCMenuAction( - title: NSLocalizedString("_details_", comment: ""), - icon: utility.loadImage(named: "info.circle", colors: [NCBrandColor.shared.iconImageColor]), - order: 10, - sender: sender, - action: { _ in - NCCreate().createShare(viewController: self, metadata: metadata, page: .activity) - } - ) - ) - } - - if metadata.lock { - var lockOwnerName = metadata.lockOwnerDisplayName.isEmpty ? metadata.lockOwner : metadata.lockOwnerDisplayName - var lockIcon = utility.loadUserImage(for: metadata.lockOwner, displayName: lockOwnerName, urlBase: metadata.urlBase) - if metadata.lockOwnerType != 0 { - lockOwnerName += " app" - if !metadata.lockOwnerEditor.isEmpty, let appIcon = UIImage(named: metadata.lockOwnerEditor) { - lockIcon = appIcon - } - } - - var lockTimeString: String? - if let lockTime = metadata.lockTimeOut { - if lockTime >= Date().addingTimeInterval(60), - let timeInterval = (lockTime.timeIntervalSince1970 - Date().timeIntervalSince1970).format() { - lockTimeString = String(format: NSLocalizedString("_time_remaining_", comment: ""), timeInterval) - } else if lockTime > Date() { - lockTimeString = NSLocalizedString("_less_a_minute_", comment: "") - } // else: don't show negative time - } - if let lockTime = metadata.lockTime, lockTimeString == nil { - lockTimeString = DateFormatter.localizedString(from: lockTime, dateStyle: .short, timeStyle: .short) - } - - actions.append( - NCMenuAction( - title: String(format: NSLocalizedString("_locked_by_", comment: ""), lockOwnerName), - details: lockTimeString, - icon: lockIcon, - order: 20, - sender: sender, - action: nil) - ) - } - - // - // VIEW IN FOLDER - // - if NCNetworking.shared.isOnline, - layoutKey != NCGlobal.shared.layoutViewFiles { - actions.append( - NCMenuAction( - title: NSLocalizedString("_view_in_folder_", comment: ""), - icon: utility.loadImage(named: "questionmark.folder", colors: [NCBrandColor.shared.iconImageColor]), - order: 21, - sender: sender, - action: { _ in - NCNetworking.shared.openFileViewInFolder(serverUrl: metadata.serverUrl, fileNameBlink: metadata.fileName, fileNameOpen: nil, sceneIdentifier: sceneIdentifier) - } - ) - ) - } - - // - // LOCK / UNLOCK - // - if NCNetworking.shared.isOnline, - !metadata.directory, - metadata.canUnlock(as: metadata.userId), - !capabilities.filesLockVersion.isEmpty { - actions.append(NCMenuAction.lockUnlockFiles(shouldLock: !metadata.lock, metadatas: [metadata], order: 30, sender: sender)) - } - - // - // SET FOLDER E2EE - // - if NCNetworking.shared.isOnline, - metadata.directory, - metadata.size == 0, - !metadata.e2eEncrypted, - NCPreferences().isEndToEndEnabled(account: metadata.account), - metadata.serverUrl == NCUtilityFileSystem().getHomeServer(urlBase: metadata.urlBase, userId: metadata.userId) { - actions.append( - NCMenuAction( - title: NSLocalizedString("_e2e_set_folder_encrypted_", comment: ""), - icon: utility.loadImage(named: "lock", colors: [NCBrandColor.shared.iconImageColor]), - order: 30, - sender: sender, - action: { _ in - Task { - let error = await NCNetworkingE2EEMarkFolder().markFolderE2ee(account: metadata.account, serverUrlFileName: metadata.serverUrlFileName, userId: metadata.userId) - if error != .success { - NCContentPresenter().showError(error: error) - } - } - } - ) - ) - } - - // - // UNSET FOLDER E2EE - // - if NCNetworking.shared.isOnline, - metadata.canUnsetDirectoryAsE2EE { - actions.append( - NCMenuAction( - title: NSLocalizedString("_e2e_remove_folder_encrypted_", comment: ""), - icon: utility.loadImage(named: "lock", colors: [NCBrandColor.shared.iconImageColor]), - order: 30, - sender: sender, - action: { _ in - Task { - let results = await NextcloudKit.shared.markE2EEFolderAsync(fileId: metadata.fileId, delete: true, account: metadata.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: metadata.account, - path: metadata.fileId, - name: "markE2EEFolder") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } - if results.error == .success { - await self.database.deleteE2eEncryptionAsync(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrlFileName)) - await self.database.setMetadataEncryptedAsync(ocId: metadata.ocId, encrypted: false) - await self.reloadDataSource() - } else { - NCContentPresenter().messageNotification(NSLocalizedString("_e2e_error_", comment: ""), error: results.error, delay: NCGlobal.shared.dismissAfterSecond, type: .error) - } - } - } - ) - ) - } - - // - // FAVORITE - if !metadata.lock { - actions.append( - NCMenuAction( - title: metadata.favorite ? NSLocalizedString("_remove_favorites_", comment: "") : NSLocalizedString("_add_favorites_", comment: ""), - icon: utility.loadImage(named: metadata.favorite ? "star.slash" : "star", colors: [NCBrandColor.shared.yellowFavorite]), - order: 50, - sender: sender, - action: { _ in - NCNetworking.shared.setStatusWaitFavorite(metadata) { error in - if error != .success { - NCContentPresenter().showError(error: error) - } - } - } - ) - ) - } - - // - // OFFLINE - // - if NCNetworking.shared.isOnline, - metadata.canSetAsAvailableOffline { - actions.append(.setAvailableOfflineAction(selectedMetadatas: [metadata], isAnyOffline: isOffline, viewController: self, order: 60, sender: sender, completion: { - Task { - await self.reloadDataSource() - } - })) - } - - // - // SHARE - // - if (NCNetworking.shared.isOnline || (tableLocalFile != nil && fileExists)) && metadata.canShare { - actions.append(.share(selectedMetadatas: [metadata], controller: self.controller, order: 80, sender: sender)) - } - - // - // SAVE LIVE PHOTO - // - if NCNetworking.shared.isOnline, - let metadataMOV = database.getMetadataLivePhoto(metadata: metadata) { - actions.append( - NCMenuAction( - title: NSLocalizedString("_livephoto_save_", comment: ""), - icon: NCUtility().loadImage(named: "livephoto", colors: [NCBrandColor.shared.iconImageColor]), - order: 100, - sender: sender, - action: { _ in - NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV, controller: self.tabBarController)) - } - ) - ) - } - - // - // SAVE AS SCAN - // - if NCNetworking.shared.isOnline, - metadata.isSavebleAsImage { - actions.append( - NCMenuAction( - title: NSLocalizedString("_save_as_scan_", comment: ""), - icon: utility.loadImage(named: "doc.viewfinder", colors: [NCBrandColor.shared.iconImageColor]), - order: 110, - sender: sender, - action: { _ in - Task { - if self.utilityFileSystem.fileProviderStorageExists(metadata) { - await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferChange(status: NCGlobal.shared.networkingStatusDownloaded, - account: metadata.account, - fileName: metadata.fileName, - serverUrl: metadata.serverUrl, - selector: NCGlobal.shared.selectorSaveAsScan, - ocId: metadata.ocId, - destination: nil, - error: .success) - } - } else { - if let metadata = await self.database.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, - session: NCNetworking.shared.sessionDownload, - selector: NCGlobal.shared.selectorSaveAsScan, - sceneIdentifier: sceneIdentifier) { - await NCNetworking.shared.downloadFile(metadata: metadata) - } - } - } - } - ) - ) - } - - // - // RENAME - // - if metadata.isRenameable { - actions.append( - NCMenuAction( - title: NSLocalizedString("_rename_", comment: ""), - icon: utility.loadImage(named: "text.cursor", colors: [NCBrandColor.shared.iconImageColor]), - order: 120, - sender: sender, - action: { _ in - Task { @MainActor in - let capabilities = await NKCapabilities.shared.getCapabilities(for: metadata.account) - let fileNameNew = await UIAlertController.renameFileAsync(fileName: metadata.fileNameView, isDirectory: metadata.directory, capabilities: capabilities, account: metadata.account, presenter: self) - - if await NCManageDatabase.shared.getMetadataAsync(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", metadata.account, metadata.serverUrl, fileNameNew)) != nil { - NCContentPresenter().showError(error: NKError(errorCode: 0, errorDescription: "_rename_already_exists_")) - return - } - - NCNetworking.shared.setStatusWaitRename(metadata, fileNameNew: fileNameNew) - } - } - ) - ) - } - - // - // COPY - MOVE - // - if metadata.isCopyableMovable { - actions.append(.moveOrCopyAction(selectedMetadatas: [metadata], account: metadata.account, viewController: self, order: 130, sender: sender)) - } - - // - // MODIFY WITH QUICK LOOK - // - if NCNetworking.shared.isOnline, - metadata.isModifiableWithQuickLook { - actions.append( - NCMenuAction( - title: NSLocalizedString("_modify_", comment: ""), - icon: utility.loadImage(named: "pencil.tip.crop.circle", colors: [NCBrandColor.shared.iconImageColor]), - order: 150, - sender: sender, - action: { _ in - Task { - if self.utilityFileSystem.fileProviderStorageExists(metadata) { - await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferChange(status: NCGlobal.shared.networkingStatusDownloaded, - account: metadata.account, - fileName: metadata.fileName, - serverUrl: metadata.serverUrl, - selector: NCGlobal.shared.selectorLoadFileQuickLook, - ocId: metadata.ocId, - destination: nil, - error: .success) - } - } else { - if let metadata = await self.database.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, - session: NCNetworking.shared.sessionDownload, - selector: NCGlobal.shared.selectorLoadFileQuickLook, - sceneIdentifier: sceneIdentifier) { - await NCNetworking.shared.downloadFile(metadata: metadata) - } - } - } - } - ) - ) - } - - // - // COLOR FOLDER - // - if self is NCFiles, - metadata.directory { - actions.append( - NCMenuAction( - title: NSLocalizedString("_change_color_", comment: ""), - icon: utility.loadImage(named: "paintpalette", colors: [NCBrandColor.shared.iconImageColor]), - order: 160, - sender: sender, - action: { _ in - if let picker = UIStoryboard(name: "NCColorPicker", bundle: nil).instantiateInitialViewController() as? NCColorPicker { - picker.metadata = metadata - picker.collectionViewCommon = self - let popup = NCPopupViewController(contentController: picker, popupWidth: 200, popupHeight: 320) - popup.backgroundAlpha = 0 - self.present(popup, animated: true) - } - } - ) - ) - } - - // - // DELETE - // - if metadata.isDeletable { - actions.append(.deleteOrUnshareAction(selectedMetadatas: [metadata], metadataFolder: metadataFolder, controller: self.controller, order: 170, sender: sender)) - } - - if let apps = capabilities.clientIntegration?.apps { - for (appName, context) in apps { - for item in context.contextMenu { - if item.mimetypeFilters == nil || (item.mimetypeFilters?.contains(metadata.contentType) == true) { - let iconImage: UIImage - if let iconUrl = item.icon, let source = SVGKSourceURL.source(from: URL(string: metadata.urlBase + iconUrl)) { - iconImage = SVGKImage(source: source)?.uiImage ?? UIImage() - } else { - iconImage = utility.loadImage(named: "testtube.2", colors: [NCBrandColor.shared.presentationIconColor]) - } - - actions.append( - NCMenuAction( - title: item.name, - icon: iconImage, - order: Int.max, - sender: sender, - action: { _ in - Task { - await NextcloudKit.shared.sendRequestAsync(account: metadata.account, - fileId: metadata.fileId, - filePath: metadata.path, - url: item.url, - method: item.method, - params: item.params) - } - } - ) - ) - } - } - } - } - - // capabilities.declarativeUI?.contextMenu.forEach { item in - // actions.append( - // NCMenuAction( - // title: item.title, -// icon: utility.loadImage(named: "testtube.2", colors: [NCBrandColor.shared.presentationIconColor]), -// order: Int.max, -// sender: sender, -// action: { _ in -// -// } -// ) -// ) -// } - - applicationHandle.addCollectionViewCommonMenu(metadata: metadata, image: image, actions: &actions) - - presentMenu(with: actions, controller: controller, sender: sender) - } -} - -extension TimeInterval { - func format() -> String? { - let formatter = DateComponentsFormatter() - formatter.allowedUnits = [.day, .hour, .minute] - formatter.unitsStyle = .full - formatter.maximumUnitCount = 1 - return formatter.string(from: self) - } -} - diff --git a/iOSClient/Menu/NCContextMenu.swift b/iOSClient/Menu/NCContextMenu.swift index be91d4b94a..c134afd7e6 100644 --- a/iOSClient/Menu/NCContextMenu.swift +++ b/iOSClient/Menu/NCContextMenu.swift @@ -35,22 +35,24 @@ class NCContextMenu: NSObject { func viewMenu() -> UIMenu { let database = NCManageDatabase.shared - guard let metadata = database.getMetadataFromOcId(metadata.ocId), - let capabilities = NCNetworking.shared.capabilities[metadata.account] else { - return UIMenu() - } + guard let metadata = database.getMetadataFromOcId(metadata.ocId), + let capabilities = NCNetworking.shared.capabilities[metadata.account] else { + return UIMenu() + } - var menuElements: [UIMenuElement] = [] - let localFile = database.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) - let isOffline = localFile?.offline == true + var shortMenu: [UIMenuElement] = [] +// var menu: [UIMenuElement] = [] + var deleteMenu: [UIMenuElement] = [] + var mainActionsMenu: [UIMenuElement] = [] + var clientIntegrationMenu: [UIMenuElement] = [] + + let localFile = database.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) + let isOffline = localFile?.offline == true var downloadRequest: DownloadRequest? - var titleDeleteConfirmFile = NSLocalizedString("_delete_file_", comment: "") let metadataMOV = self.database.getMetadataLivePhoto(metadata: metadata) let scene = SceneManager.shared.getWindow(sceneIdentifier: sceneIdentifier)?.windowScene - if metadata.directory { titleDeleteConfirmFile = NSLocalizedString("_delete_folder_", comment: "") } - // MENU ITEMS let detail = UIAction(title: NSLocalizedString("_details_", comment: ""), @@ -60,7 +62,7 @@ class NCContextMenu: NSObject { let favorite = UIAction(title: metadata.favorite ? NSLocalizedString("_remove_favorites_", comment: "") : - NSLocalizedString("_add_favorites_", comment: ""), + NSLocalizedString("_add_favorites_", comment: ""), image: utility.loadImage(named: self.metadata.favorite ? "star.slash.fill" : "star.fill", colors: [NCBrandColor.shared.yellowFavorite])) { _ in self.networking.setStatusWaitFavorite(self.metadata) { error in if error != .success { @@ -118,7 +120,7 @@ class NCContextMenu: NSObject { if let request = downloadRequest { request.cancel() } - } + } let results = await self.networking.downloadFile(metadata: metadata) { request in downloadRequest = request @@ -138,29 +140,19 @@ class NCContextMenu: NSObject { } } - let deleteConfirmFile = UIAction(title: titleDeleteConfirmFile, + let deleteConfirmFile = UIAction(title: NSLocalizedString(metadata.directory ? "_delete_folder_" : "_delete_file_", comment: ""), image: utility.loadImage(named: "trash"), attributes: .destructive) { _ in - var alertStyle = UIAlertController.Style.actionSheet - if UIDevice.current.userInterfaceIdiom == .pad { - alertStyle = .alert - } - let alertController = UIAlertController(title: nil, message: nil, preferredStyle: alertStyle) - alertController.addAction(UIAlertAction(title: NSLocalizedString("_delete_file_", comment: ""), style: .destructive) { _ in - if let viewController = self.viewController as? NCCollectionViewCommon { - Task { - await self.networking.setStatusWaitDelete(metadatas: [self.metadata], sceneIdentifier: self.sceneIdentifier) - await viewController.reloadDataSource() - } + if let viewController = self.viewController as? NCCollectionViewCommon { + Task { + await self.networking.setStatusWaitDelete(metadatas: [self.metadata], sceneIdentifier: self.sceneIdentifier) + await viewController.reloadDataSource() } - if let viewController = self.viewController as? NCMedia { - Task { - await viewController.deleteImage(with: self.metadata.ocId) - } + } else if let viewController = self.viewController as? NCMedia { + Task { + await viewController.deleteImage(with: self.metadata.ocId) } - }) - alertController.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel) { _ in }) - self.viewController.present(alertController, animated: true, completion: nil) + } } let deleteConfirmLocal = UIAction(title: NSLocalizedString("_remove_local_file_", comment: ""), @@ -181,7 +173,7 @@ class NCContextMenu: NSObject { } } - let deleteSubMenu = UIMenu(title: NSLocalizedString("_delete_file_", comment: ""), + let deleteSubMenu = UIMenu(title: NSLocalizedString("_delete_", comment: ""), image: utility.loadImage(named: "trash"), options: .destructive, children: [deleteConfirmLocal, deleteConfirmFile]) @@ -193,7 +185,7 @@ class NCContextMenu: NSObject { !metadata.directory, metadata.canUnlock(as: metadata.userId), !capabilities.filesLockVersion.isEmpty { - menuElements.append(ContextMenuActions.lockUnlock(shouldLock: !metadata.lock, metadatas: [metadata])) + mainActionsMenu.append(ContextMenuActions.lockUnlock(shouldLock: !metadata.lock, metadatas: [metadata])) } // @@ -222,7 +214,7 @@ class NCContextMenu: NSObject { } } - menuElements.append(action) + mainActionsMenu.append(action) } // @@ -268,7 +260,7 @@ class NCContextMenu: NSObject { } } - menuElements.append(action) + mainActionsMenu.append(action) } // @@ -277,7 +269,7 @@ class NCContextMenu: NSObject { if NCNetworking.shared.isOnline, metadata.canSetAsAvailableOffline { - menuElements.append(ContextMenuActions.setAvailableOffline(selectedMetadatas: [metadata], isAnyOffline: isOffline, viewController: viewController)) + mainActionsMenu.append(ContextMenuActions.setAvailableOffline(selectedMetadatas: [metadata], isAnyOffline: isOffline, viewController: viewController)) } @@ -316,7 +308,7 @@ class NCContextMenu: NSObject { } } - menuElements.append(action) + mainActionsMenu.append(action) } // @@ -350,11 +342,11 @@ class NCContextMenu: NSObject { } } - menuElements.append(action) + mainActionsMenu.append(action) } if metadata.isCopyableMovable { - menuElements.append(ContextMenuActions.moveOrCopy(selectedMetadatas: [metadata], account: metadata.account, viewController: viewController)) + mainActionsMenu.append(ContextMenuActions.moveOrCopy(selectedMetadatas: [metadata], account: metadata.account, viewController: viewController)) } // @@ -392,7 +384,7 @@ class NCContextMenu: NSObject { } } - menuElements.append(action) + mainActionsMenu.append(action) } // @@ -420,7 +412,7 @@ class NCContextMenu: NSObject { } } - menuElements.append(action) + mainActionsMenu.append(action) } // @@ -507,7 +499,7 @@ class NCContextMenu: NSObject { } } - menuElements.append(deferredElement) + clientIntegrationMenu.append(deferredElement) } } } @@ -515,53 +507,43 @@ class NCContextMenu: NSObject { // ------ MENU ----- - var menu: [UIMenuElement] = [] - if self.networking.isOnline { + let hasLivePhoto = self.database.getMetadataLivePhoto(metadata: metadata) != nil + let isMediaViewController = viewController is NCMedia + if metadata.directory { - if metadata.isDirectoryE2EE || metadata.e2eEncrypted { - menu.append(favorite) - } else { - menu.append(favorite) - menu.append(deleteConfirmFile) + if !metadata.isDirectoryE2EE && !metadata.e2eEncrypted { + deleteMenu.append(deleteSubMenu) } - return UIMenu(title: "", children: [detail, UIMenu(title: "", options: .displayInline, children: menu + menuElements)]) } else { - if metadata.lock { -// menu.append(favorite) -// menu.append(share) - - if self.database.getMetadataLivePhoto(metadata: metadata) != nil { - menu.append(livePhotoSave) - } - } else { -// menu.append(favorite) -// menu.append(share) - - if self.database.getMetadataLivePhoto(metadata: metadata) != nil { - menu.append(livePhotoSave) - } + if hasLivePhoto { + mainActionsMenu.append(livePhotoSave) + } - if viewController is NCMedia { - menu.append(viewInFolder) + if !metadata.lock { + if isMediaViewController { + mainActionsMenu.append(viewInFolder) } - // MODIFY WITH QUICK LOOK if metadata.isModifiableWithQuickLook { - menu.append(modify) + mainActionsMenu.append(modify) } - if viewController is NCMedia { - menu.append(deleteConfirmFile) - } else { - menu.append(deleteSubMenu) - } + deleteMenu.append(deleteSubMenu) } - - let finalMenu = UIMenu(title: "", children: [detail, share, favorite, UIMenu(title: "", options: .displayInline, children: menu + menuElements)]) - finalMenu.preferredElementSize = .medium - return finalMenu } + + let baseChildren = [ + UIMenu(title: "", options: .displayAsPalette, children: shortMenu), + UIMenu(title: "", options: .displayInline, children: mainActionsMenu), + UIMenu(title: "", options: .displayInline, children: clientIntegrationMenu), + UIMenu(title: "", options: .displayInline, children: deleteMenu) + ] + + let finalMenu = UIMenu(title: "", children: (metadata.lock ? [detail] : [detail, share, favorite]) + baseChildren) + finalMenu.preferredElementSize = .medium + + return finalMenu } else { return UIMenu() } From ff7b7a0df4a89e5c3614965b32d41805ff3023ad Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 10 Dec 2025 15:47:14 +0100 Subject: [PATCH 19/63] Sendable fix Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCContextMenu.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iOSClient/Menu/NCContextMenu.swift b/iOSClient/Menu/NCContextMenu.swift index c134afd7e6..c319ebc893 100644 --- a/iOSClient/Menu/NCContextMenu.swift +++ b/iOSClient/Menu/NCContextMenu.swift @@ -477,12 +477,14 @@ class NCContextMenu: NSObject { if let tooltip = response.uiResponse?.ocs.data.tooltip { NCContentPresenter().showCustomMessage(message: tooltip, type: .success) } else { + let baseURL = metadata.urlBase + await MainActor.run { guard let ui = response.uiResponse?.ocs.data.root, let firstRow = ui.rows.first, let child = firstRow.children.first else { return } let viewer = ClientIntegrationUIViewer( rows: [.init(element: child.element, title: child.text, urlString: child.url)], - baseURL: metadata.urlBase + baseURL: baseURL ) let hosting = UIHostingController(rootView: viewer) hosting.modalPresentationStyle = .pageSheet From 2a05902f0b93a2c1834e580a23460e36b6e9e209 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 10 Dec 2025 15:55:27 +0100 Subject: [PATCH 20/63] Refactor Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCContextMenu.swift | 182 +++++++++++++++-------------- 1 file changed, 94 insertions(+), 88 deletions(-) diff --git a/iOSClient/Menu/NCContextMenu.swift b/iOSClient/Menu/NCContextMenu.swift index c319ebc893..5ed70aa616 100644 --- a/iOSClient/Menu/NCContextMenu.swift +++ b/iOSClient/Menu/NCContextMenu.swift @@ -418,94 +418,7 @@ class NCContextMenu: NSObject { // // CLIENT INTEGRATION // - if let apps = capabilities.clientIntegration?.apps { - for (_, context) in apps { - for item in context.contextMenu { - var shouldShowMenu = false - - if let mimetypeFilters = item.mimetypeFilters { - let filters = mimetypeFilters.split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) } - shouldShowMenu = filters.contains(where: { filter in // If app has specific mimetypes, we should only show the menu if the file/folder matches one of them. - if filter.hasSuffix("/") { - // Handle wildcard MIME types like "audio/", "video/", "image/" - return metadata.contentType.hasPrefix(filter) - } else { - return metadata.contentType == filter - } - }) - } else { - shouldShowMenu = true // if app has no mimetypes, then menu should be shown for every file/folder - } - - if shouldShowMenu { - let deferredElement = UIDeferredMenuElement { completion in - Task { - var iconImage: UIImage - if let iconUrl = item.icon, - let url = URL(string: metadata.urlBase + iconUrl) { - do { - let (data, _) = try await URLSession.shared.data(from: url) - iconImage = SVGKImage(data: data)?.uiImage.withRenderingMode(.alwaysTemplate) ?? UIImage() - } catch { - iconImage = self.utility.loadImage( - named: "testtube.2", - colors: [NCBrandColor.shared.presentationIconColor] - ) - } - } else { - iconImage = self.utility.loadImage( - named: "testtube.2", - colors: [NCBrandColor.shared.presentationIconColor] - ) - } - - let action = await UIAction( - title: item.name, - image: iconImage - ) { _ in - Task { - let response = await NextcloudKit.shared.sendRequestAsync(account: metadata.account, - fileId: metadata.fileId, - filePath: self.utilityFileSystem.getRelativeFilePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId), - url: item.url, - method: item.method, - params: item.params) - - if response.error != .success { - NCContentPresenter().showError(error: response.error) - } else { - if let tooltip = response.uiResponse?.ocs.data.tooltip { - NCContentPresenter().showCustomMessage(message: tooltip, type: .success) - } else { - let baseURL = metadata.urlBase - - await MainActor.run { - guard let ui = response.uiResponse?.ocs.data.root, let firstRow = ui.rows.first, let child = firstRow.children.first else { return } - - let viewer = ClientIntegrationUIViewer( - rows: [.init(element: child.element, title: child.text, urlString: child.url)], - baseURL: baseURL - ) - let hosting = UIHostingController(rootView: viewer) - hosting.modalPresentationStyle = .pageSheet - self.viewController.present(hosting, animated: true) - } - } - } - } - } - - await MainActor.run { - completion([action]) - } - } - } - - clientIntegrationMenu.append(deferredElement) - } - } - } - } + buildClientIntegrationMenuItems(capabilities: capabilities, metadata: metadata, clientIntegrationMenu: &clientIntegrationMenu) // ------ MENU ----- @@ -550,4 +463,97 @@ class NCContextMenu: NSObject { return UIMenu() } } + + // MARK: - Helper Methods + + private func buildClientIntegrationMenuItems(capabilities: NKCapabilities.Capabilities, metadata: tableMetadata, clientIntegrationMenu: inout [UIMenuElement]) { + guard let apps = capabilities.clientIntegration?.apps else { return } + + for (_, context) in apps { + for item in context.contextMenu { + var shouldShowMenu = false + + if let mimetypeFilters = item.mimetypeFilters { + let filters = mimetypeFilters.split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) } + shouldShowMenu = filters.contains(where: { filter in // If app has specific mimetypes, we should only show the menu if the file/folder matches one of them. + if filter.hasSuffix("/") { + // Handle wildcard MIME types like "audio/", "video/", "image/" + return metadata.contentType.hasPrefix(filter) + } else { + return metadata.contentType == filter + } + }) + } else { + shouldShowMenu = true // if app has no mimetypes, then menu should be shown for every file/folder + } + + if shouldShowMenu { + let deferredElement = UIDeferredMenuElement { completion in + Task { + var iconImage: UIImage + if let iconUrl = item.icon, + let url = URL(string: metadata.urlBase + iconUrl) { + do { + let (data, _) = try await URLSession.shared.data(from: url) + iconImage = SVGKImage(data: data)?.uiImage.withRenderingMode(.alwaysTemplate) ?? UIImage() + } catch { + iconImage = self.utility.loadImage( + named: "testtube.2", + colors: [NCBrandColor.shared.presentationIconColor] + ) + } + } else { + iconImage = self.utility.loadImage( + named: "testtube.2", + colors: [NCBrandColor.shared.presentationIconColor] + ) + } + + let action = await UIAction( + title: item.name, + image: iconImage + ) { _ in + Task { + let response = await NextcloudKit.shared.sendRequestAsync(account: metadata.account, + fileId: metadata.fileId, + filePath: self.utilityFileSystem.getRelativeFilePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId), + url: item.url, + method: item.method, + params: item.params) + + if response.error != .success { + NCContentPresenter().showError(error: response.error) + } else { + if let tooltip = response.uiResponse?.ocs.data.tooltip { + NCContentPresenter().showCustomMessage(message: tooltip, type: .success) + } else { + let baseURL = metadata.urlBase + + await MainActor.run { + guard let ui = response.uiResponse?.ocs.data.root, let firstRow = ui.rows.first, let child = firstRow.children.first else { return } + + let viewer = ClientIntegrationUIViewer( + rows: [.init(element: child.element, title: child.text, urlString: child.url)], + baseURL: baseURL + ) + let hosting = UIHostingController(rootView: viewer) + hosting.modalPresentationStyle = .pageSheet + self.viewController.present(hosting, animated: true) + } + } + } + } + } + + await MainActor.run { + completion([action]) + } + } + } + + clientIntegrationMenu.append(deferredElement) + } + } + } + } } From 6b234dfd5182b67fd2324219b866f669c0e913a0 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 10 Dec 2025 16:04:45 +0100 Subject: [PATCH 21/63] Refactor Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCContextMenu.swift | 696 +++++++++++++++-------------- 1 file changed, 357 insertions(+), 339 deletions(-) diff --git a/iOSClient/Menu/NCContextMenu.swift b/iOSClient/Menu/NCContextMenu.swift index 5ed70aa616..d7537a5398 100644 --- a/iOSClient/Menu/NCContextMenu.swift +++ b/iOSClient/Menu/NCContextMenu.swift @@ -40,8 +40,6 @@ class NCContextMenu: NSObject { return UIMenu() } - var shortMenu: [UIMenuElement] = [] -// var menu: [UIMenuElement] = [] var deleteMenu: [UIMenuElement] = [] var mainActionsMenu: [UIMenuElement] = [] var clientIntegrationMenu: [UIMenuElement] = [] @@ -49,422 +47,442 @@ class NCContextMenu: NSObject { let localFile = database.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) let isOffline = localFile?.offline == true - var downloadRequest: DownloadRequest? - let metadataMOV = self.database.getMetadataLivePhoto(metadata: metadata) - let scene = SceneManager.shared.getWindow(sceneIdentifier: sceneIdentifier)?.windowScene + // Build top menu items + let detail = makeDetailAction(metadata: metadata) + let favorite = makeFavoriteAction(metadata: metadata) + let share = makeShareAction() - // MENU ITEMS + buildMainActionsMenu( + metadata: metadata, + capabilities: capabilities, + isOffline: isOffline, + mainActionsMenu: &mainActionsMenu + ) - let detail = UIAction(title: NSLocalizedString("_details_", comment: ""), - image: utility.loadImage(named: "info.circle.fill")) { _ in - NCCreate().createShare(viewController: self.viewController, metadata: self.metadata, page: .activity) + buildClientIntegrationMenuItems( + capabilities: capabilities, + metadata: metadata, + clientIntegrationMenu: &clientIntegrationMenu + ) + + buildDeleteMenu(metadata: metadata, deleteMenu: &deleteMenu) + + // Assemble final menu + if self.networking.isOnline { + let baseChildren = [ + UIMenu(title: "", options: .displayInline, children: mainActionsMenu), + UIMenu(title: "", options: .displayInline, children: clientIntegrationMenu), + UIMenu(title: "", options: .displayInline, children: deleteMenu) + ] + + let finalMenu = UIMenu(title: "", children: (metadata.lock ? [detail] : [detail, share, favorite]) + baseChildren) + finalMenu.preferredElementSize = .medium + + return finalMenu + } else { + return UIMenu() + } + } + + // MARK: Basic Actions + + private func makeDetailAction(metadata: tableMetadata) -> UIAction { + return UIAction( + title: NSLocalizedString("_details_", comment: ""), + image: utility.loadImage(named: "info.circle.fill") + ) { _ in + NCCreate().createShare(viewController: self.viewController, metadata: metadata, page: .activity) } + } - let favorite = UIAction(title: metadata.favorite ? - NSLocalizedString("_remove_favorites_", comment: "") : - NSLocalizedString("_add_favorites_", comment: ""), - image: utility.loadImage(named: self.metadata.favorite ? "star.slash.fill" : "star.fill", colors: [NCBrandColor.shared.yellowFavorite])) { _ in - self.networking.setStatusWaitFavorite(self.metadata) { error in + private func makeFavoriteAction(metadata: tableMetadata) -> UIAction { + return UIAction( + title: metadata.favorite ? + NSLocalizedString("_remove_favorites_", comment: "") : + NSLocalizedString("_add_favorites_", comment: ""), + image: utility.loadImage( + named: metadata.favorite ? "star.slash.fill" : "star.fill", + colors: [NCBrandColor.shared.yellowFavorite] + ) + ) { _ in + self.networking.setStatusWaitFavorite(metadata) { error in if error != .success { NCContentPresenter().showError(error: error) } } } + } - let share = UIAction(title: NSLocalizedString("_share_", comment: ""), - image: utility.loadImage(named: "square.and.arrow.up.fill") ) { _ in - Task {@MainActor in + private func makeShareAction() -> UIAction { + return UIAction( + title: NSLocalizedString("_share_", comment: ""), + image: utility.loadImage(named: "square.and.arrow.up.fill") + ) { _ in + Task { @MainActor in let controller = self.viewController.tabBarController as? NCMainTabBarController - await NCCreate().createActivityViewController(selectedMetadata: [self.metadata], - controller: controller, - sender: self.sender) + await NCCreate().createActivityViewController( + selectedMetadata: [self.metadata], + controller: controller, + sender: self.sender + ) } } + } - let viewInFolder = UIAction(title: NSLocalizedString("_view_in_folder_", comment: ""), - image: utility.loadImage(named: "questionmark.folder")) { _ in - NCNetworking.shared.openFileViewInFolder(serverUrl: self.metadata.serverUrl, fileNameBlink: self.metadata.fileName, fileNameOpen: nil, sceneIdentifier: self.sceneIdentifier) - } + // MARK: Main Actions Menu - let livePhotoSave = UIAction(title: NSLocalizedString("_livephoto_save_", comment: ""), image: utility.loadImage(named: "livephoto")) { _ in - if let metadataMOV = metadataMOV { - self.networking.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: self.metadata, metadataMOV: metadataMOV, controller: self.viewController.tabBarController)) - } + private func buildMainActionsMenu( + metadata: tableMetadata, + capabilities: NKCapabilities.Capabilities, + isOffline: Bool, + mainActionsMenu: inout [UIMenuElement] + ) { + // Lock/Unlock + if NCNetworking.shared.isOnline, + !metadata.directory, + metadata.canUnlock(as: metadata.userId), + !capabilities.filesLockVersion.isEmpty { + mainActionsMenu.append( + ContextMenuActions.lockUnlock(shouldLock: !metadata.lock, metadatas: [metadata]) + ) } - let modify = UIAction(title: NSLocalizedString("_modify_", comment: ""), - image: utility.loadImage(named: "pencil.tip.crop.circle")) { _ in - Task { @MainActor in - if self.utilityFileSystem.fileProviderStorageExists(self.metadata) { - await self.networking.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferChange(status: self.global.networkingStatusDownloaded, - account: self.metadata.account, - fileName: self.metadata.fileName, - serverUrl: self.metadata.serverUrl, - selector: self.global.selectorLoadFileQuickLook, - ocId: self.metadata.ocId, - destination: nil, - error: .success) - } - } else { - guard let metadata = await self.database.setMetadataSessionInWaitDownloadAsync(ocId: self.metadata.ocId, - session: self.networking.sessionDownload, - selector: self.global.selectorLoadFileQuickLook, - sceneIdentifier: self.sceneIdentifier) else { - return - } - - let token = showHudBanner( - scene: scene, - title: NSLocalizedString("_download_in_progress_", comment: "")) { _, _ in - if let request = downloadRequest { - request.cancel() - } - } - - let results = await self.networking.downloadFile(metadata: metadata) { request in - downloadRequest = request - } progressHandler: { progress in - Task {@MainActor in - LucidBanner.shared.update(progress: progress.fractionCompleted, for: token) - } - } - LucidBanner.shared.dismiss() + // E2EE actions + addE2EEActions(metadata: metadata, capabilities: capabilities, mainActionsMenu: &mainActionsMenu) - if results.nkError == .success || results.afError?.isExplicitlyCancelledError ?? false { - // - } else { - await showErrorBanner(scene: scene, errorDescription: results.nkError.errorDescription, errorCode: results.nkError.errorCode) - } - } - } + // Offline + if NCNetworking.shared.isOnline, + metadata.canSetAsAvailableOffline { + mainActionsMenu.append( + ContextMenuActions.setAvailableOffline( + selectedMetadatas: [metadata], + isAnyOffline: isOffline, + viewController: viewController + ) + ) } - let deleteConfirmFile = UIAction(title: NSLocalizedString(metadata.directory ? "_delete_folder_" : "_delete_file_", comment: ""), - image: utility.loadImage(named: "trash"), attributes: .destructive) { _ in - - if let viewController = self.viewController as? NCCollectionViewCommon { - Task { - await self.networking.setStatusWaitDelete(metadatas: [self.metadata], sceneIdentifier: self.sceneIdentifier) - await viewController.reloadDataSource() - } - } else if let viewController = self.viewController as? NCMedia { - Task { - await viewController.deleteImage(with: self.metadata.ocId) - } - } + // Save as scan + if NCNetworking.shared.isOnline, + metadata.isSavebleAsImage { + mainActionsMenu.append(makeSaveAsScanAction(metadata: metadata)) } - let deleteConfirmLocal = UIAction(title: NSLocalizedString("_remove_local_file_", comment: ""), - image: utility.loadImage(named: "trash"), attributes: .destructive) { _ in - Task { - let error = await self.networking.deleteCache(self.metadata, sceneIdentifier: self.sceneIdentifier) - - await self.networking.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferChange(status: NCGlobal.shared.networkingStatusDelete, - account: self.metadata.account, - fileName: self.metadata.fileName, - serverUrl: self.metadata.serverUrl, - selector: self.metadata.sessionSelector, - ocId: self.metadata.ocId, - destination: nil, - error: error) - } - } + // Rename + if metadata.isRenameable { + mainActionsMenu.append(makeRenameAction(metadata: metadata)) } - let deleteSubMenu = UIMenu(title: NSLocalizedString("_delete_", comment: ""), - image: utility.loadImage(named: "trash"), - options: .destructive, - children: [deleteConfirmLocal, deleteConfirmFile]) + // Move/Copy + if metadata.isCopyableMovable { + mainActionsMenu.append( + ContextMenuActions.moveOrCopy( + selectedMetadatas: [metadata], + account: metadata.account, + viewController: viewController + ) + ) + } - // - // LOCK / UNLOCK - // + // Modify with Quick Look if NCNetworking.shared.isOnline, - !metadata.directory, - metadata.canUnlock(as: metadata.userId), - !capabilities.filesLockVersion.isEmpty { - mainActionsMenu.append(ContextMenuActions.lockUnlock(shouldLock: !metadata.lock, metadatas: [metadata])) + metadata.isModifiableWithQuickLook { + mainActionsMenu.append(makeModifyWithQuickLookAction(metadata: metadata)) } - // - // SET FOLDER E2EE - // + // Color folder + if viewController is NCFiles, + metadata.directory { + mainActionsMenu.append(makeColorFolderAction(metadata: metadata)) + } + } + + // MARK: E2EE Actions + + private func addE2EEActions( + metadata: tableMetadata, + capabilities: NKCapabilities.Capabilities, + mainActionsMenu: inout [UIMenuElement] + ) { + // Set folder E2EE if NCNetworking.shared.isOnline, metadata.directory, metadata.size == 0, !metadata.e2eEncrypted, NCPreferences().isEndToEndEnabled(account: metadata.account), metadata.serverUrl == NCUtilityFileSystem().getHomeServer(urlBase: metadata.urlBase, userId: metadata.userId) { - - let action = UIAction( - title: NSLocalizedString("_e2e_set_folder_encrypted_", comment: ""), - image: utility.loadImage(named: "lock", colors: [NCBrandColor.shared.iconImageColor]) - ) { _ in - Task { - let error = await NCNetworkingE2EEMarkFolder().markFolderE2ee( - account: metadata.account, - serverUrlFileName: metadata.serverUrlFileName, - userId: metadata.userId - ) - if error != .success { - NCContentPresenter().showError(error: error) - } - } - } - - mainActionsMenu.append(action) + mainActionsMenu.append(makeSetFolderE2EEAction(metadata: metadata)) } - // - // UNSET FOLDER E2EE - // + // Unset folder E2EE if NCNetworking.shared.isOnline, metadata.canUnsetDirectoryAsE2EE { + mainActionsMenu.append(makeUnsetFolderE2EEAction(metadata: metadata)) + } + } - let action = UIAction( - title: NSLocalizedString("_e2e_remove_folder_encrypted_", comment: ""), - image: utility.loadImage(named: "lock", colors: [NCBrandColor.shared.iconImageColor]) - ) { [self] _ in - Task { - let results = await NextcloudKit.shared.markE2EEFolderAsync( - fileId: metadata.fileId, - delete: true, - account: metadata.account - ) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier( - account: metadata.account, - path: metadata.fileId, - name: "markE2EEFolder" - ) - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } - - if results.error == .success { - await database.deleteE2eEncryptionAsync( - predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrlFileName) - ) - await database.setMetadataEncryptedAsync(ocId: metadata.ocId, encrypted: false) - await (viewController as? NCCollectionViewCommon)?.reloadDataSource() - } else { - NCContentPresenter().messageNotification( - NSLocalizedString("_e2e_error_", comment: ""), - error: results.error, - delay: NCGlobal.shared.dismissAfterSecond, - type: .error - ) - } + private func makeSetFolderE2EEAction(metadata: tableMetadata) -> UIAction { + return UIAction( + title: NSLocalizedString("_e2e_set_folder_encrypted_", comment: ""), + image: utility.loadImage(named: "lock", colors: [NCBrandColor.shared.iconImageColor]) + ) { _ in + Task { + let error = await NCNetworkingE2EEMarkFolder().markFolderE2ee( + account: metadata.account, + serverUrlFileName: metadata.serverUrlFileName, + userId: metadata.userId + ) + if error != .success { + NCContentPresenter().showError(error: error) } } - - mainActionsMenu.append(action) } + } - // - // OFFLINE - // - if NCNetworking.shared.isOnline, - metadata.canSetAsAvailableOffline { - - mainActionsMenu.append(ContextMenuActions.setAvailableOffline(selectedMetadatas: [metadata], isAnyOffline: isOffline, viewController: viewController)) + private func makeUnsetFolderE2EEAction(metadata: tableMetadata) -> UIAction { + return UIAction( + title: NSLocalizedString("_e2e_remove_folder_encrypted_", comment: ""), + image: utility.loadImage(named: "lock", colors: [NCBrandColor.shared.iconImageColor]) + ) { _ in + Task { + let results = await NextcloudKit.shared.markE2EEFolderAsync( + fileId: metadata.fileId, + delete: true, + account: metadata.account + ) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier( + account: metadata.account, + path: metadata.fileId, + name: "markE2EEFolder" + ) + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + if results.error == .success { + await self.database.deleteE2eEncryptionAsync( + predicate: NSPredicate( + format: "account == %@ AND serverUrl == %@", + metadata.account, + metadata.serverUrlFileName + ) + ) + await self.database.setMetadataEncryptedAsync(ocId: metadata.ocId, encrypted: false) + await (self.viewController as? NCCollectionViewCommon)?.reloadDataSource() + } else { + NCContentPresenter().messageNotification( + NSLocalizedString("_e2e_error_", comment: ""), + error: results.error, + delay: NCGlobal.shared.dismissAfterSecond, + type: .error + ) + } + } } + } - // - // SAVE AS SCAN - // - if NCNetworking.shared.isOnline, - metadata.isSavebleAsImage { + // MARK: File Actions - let action = UIAction( - title: NSLocalizedString("_save_as_scan_", comment: ""), - image: utility.loadImage(named: "doc.viewfinder", colors: [NCBrandColor.shared.iconImageColor]) - ) { _ in - Task { - if self.utilityFileSystem.fileProviderStorageExists(metadata) { - await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferChange(status: NCGlobal.shared.networkingStatusDownloaded, - account: metadata.account, - fileName: metadata.fileName, - serverUrl: metadata.serverUrl, - selector: NCGlobal.shared.selectorSaveAsScan, - ocId: metadata.ocId, - destination: nil, - error: .success) - } - } else { - if let metadata = await self.database.setMetadataSessionInWaitDownloadAsync( - ocId: metadata.ocId, - session: NCNetworking.shared.sessionDownload, + private func makeSaveAsScanAction(metadata: tableMetadata) -> UIAction { + return UIAction( + title: NSLocalizedString("_save_as_scan_", comment: ""), + image: utility.loadImage(named: "doc.viewfinder", colors: [NCBrandColor.shared.iconImageColor]) + ) { _ in + Task { + if self.utilityFileSystem.fileProviderStorageExists(metadata) { + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferChange( + status: NCGlobal.shared.networkingStatusDownloaded, + account: metadata.account, + fileName: metadata.fileName, + serverUrl: metadata.serverUrl, selector: NCGlobal.shared.selectorSaveAsScan, - sceneIdentifier: self.sceneIdentifier - ) { - await NCNetworking.shared.downloadFile(metadata: metadata) - } + ocId: metadata.ocId, + destination: nil, + error: .success + ) + } + } else { + if let metadata = await self.database.setMetadataSessionInWaitDownloadAsync( + ocId: metadata.ocId, + session: NCNetworking.shared.sessionDownload, + selector: NCGlobal.shared.selectorSaveAsScan, + sceneIdentifier: self.sceneIdentifier + ) { + await NCNetworking.shared.downloadFile(metadata: metadata) } } } - - mainActionsMenu.append(action) } + } - // - // RENAME - // - if metadata.isRenameable { - let action = UIAction( - title: NSLocalizedString("_rename_", comment: ""), - image: utility.loadImage(named: "text.cursor", colors: [NCBrandColor.shared.iconImageColor]) - ) { [self] _ in - Task { @MainActor in - let capabilities = await NKCapabilities.shared.getCapabilities(for: metadata.account) - let fileNameNew = await UIAlertController.renameFileAsync( - fileName: metadata.fileNameView, - isDirectory: metadata.directory, - capabilities: capabilities, - account: metadata.account, - presenter: viewController + private func makeRenameAction(metadata: tableMetadata) -> UIAction { + return UIAction( + title: NSLocalizedString("_rename_", comment: ""), + image: utility.loadImage(named: "text.cursor", colors: [NCBrandColor.shared.iconImageColor]) + ) { _ in + Task { @MainActor in + let capabilities = await NKCapabilities.shared.getCapabilities(for: metadata.account) + let fileNameNew = await UIAlertController.renameFileAsync( + fileName: metadata.fileNameView, + isDirectory: metadata.directory, + capabilities: capabilities, + account: metadata.account, + presenter: self.viewController + ) + + if await NCManageDatabase.shared.getMetadataAsync( + predicate: NSPredicate( + format: "account == %@ AND serverUrl == %@ AND fileName == %@", + metadata.account, + metadata.serverUrl, + fileNameNew ) - - if await NCManageDatabase.shared.getMetadataAsync( - predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", metadata.account, metadata.serverUrl, fileNameNew) - ) != nil { - NCContentPresenter().showError( - error: NKError(errorCode: 0, errorDescription: "_rename_already_exists_") - ) - return - } - - NCNetworking.shared.setStatusWaitRename(metadata, fileNameNew: fileNameNew) + ) != nil { + NCContentPresenter().showError( + error: NKError(errorCode: 0, errorDescription: "_rename_already_exists_") + ) + return } - } - - mainActionsMenu.append(action) - } - if metadata.isCopyableMovable { - mainActionsMenu.append(ContextMenuActions.moveOrCopy(selectedMetadatas: [metadata], account: metadata.account, viewController: viewController)) + NCNetworking.shared.setStatusWaitRename(metadata, fileNameNew: fileNameNew) + } } + } - // - // MODIFY WITH QUICK LOOK - // - if NCNetworking.shared.isOnline, - metadata.isModifiableWithQuickLook { - - let action = UIAction( - title: NSLocalizedString("_modify_", comment: ""), - image: utility.loadImage(named: "pencil.tip.crop.circle", colors: [NCBrandColor.shared.iconImageColor]) - ) { _ in - Task { - if self.utilityFileSystem.fileProviderStorageExists(metadata) { - await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferChange(status: NCGlobal.shared.networkingStatusDownloaded, - account: metadata.account, - fileName: metadata.fileName, - serverUrl: metadata.serverUrl, - selector: NCGlobal.shared.selectorLoadFileQuickLook, - ocId: metadata.ocId, - destination: nil, - error: .success) - } - } else { - if let metadata = await self.database.setMetadataSessionInWaitDownloadAsync( - ocId: metadata.ocId, - session: NCNetworking.shared.sessionDownload, + private func makeModifyWithQuickLookAction(metadata: tableMetadata) -> UIAction { + return UIAction( + title: NSLocalizedString("_modify_", comment: ""), + image: utility.loadImage(named: "pencil.tip.crop.circle", colors: [NCBrandColor.shared.iconImageColor]) + ) { _ in + Task { + if self.utilityFileSystem.fileProviderStorageExists(metadata) { + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferChange( + status: NCGlobal.shared.networkingStatusDownloaded, + account: metadata.account, + fileName: metadata.fileName, + serverUrl: metadata.serverUrl, selector: NCGlobal.shared.selectorLoadFileQuickLook, - sceneIdentifier: self.sceneIdentifier - ) { - await NCNetworking.shared.downloadFile(metadata: metadata) - } + ocId: metadata.ocId, + destination: nil, + error: .success + ) + } + } else { + if let metadata = await self.database.setMetadataSessionInWaitDownloadAsync( + ocId: metadata.ocId, + session: NCNetworking.shared.sessionDownload, + selector: NCGlobal.shared.selectorLoadFileQuickLook, + sceneIdentifier: self.sceneIdentifier + ) { + await NCNetworking.shared.downloadFile(metadata: metadata) } } } - - mainActionsMenu.append(action) } + } - // - // COLOR FOLDER - // - if viewController is NCFiles, - metadata.directory { - - let action = UIAction( - title: NSLocalizedString("_change_color_", comment: ""), - image: utility.loadImage(named: "paintpalette", colors: [NCBrandColor.shared.iconImageColor]) - ) { [self] _ in - if let picker = UIStoryboard(name: "NCColorPicker", bundle: nil) - .instantiateInitialViewController() as? NCColorPicker { - - picker.metadata = metadata - picker.collectionViewCommon = viewController as? NCFiles - let popup = NCPopupViewController( - contentController: picker, - popupWidth: 200, - popupHeight: 320 - ) - popup.backgroundAlpha = 0 - self.viewController.present(popup, animated: true) - } + private func makeColorFolderAction(metadata: tableMetadata) -> UIAction { + return UIAction( + title: NSLocalizedString("_change_color_", comment: ""), + image: utility.loadImage(named: "paintpalette", colors: [NCBrandColor.shared.iconImageColor]) + ) { _ in + if let picker = UIStoryboard(name: "NCColorPicker", bundle: nil) + .instantiateInitialViewController() as? NCColorPicker { + + picker.metadata = metadata + picker.collectionViewCommon = self.viewController as? NCFiles + let popup = NCPopupViewController( + contentController: picker, + popupWidth: 200, + popupHeight: 320 + ) + popup.backgroundAlpha = 0 + self.viewController.present(popup, animated: true) } - - mainActionsMenu.append(action) } + } - // - // CLIENT INTEGRATION - // - buildClientIntegrationMenuItems(capabilities: capabilities, metadata: metadata, clientIntegrationMenu: &clientIntegrationMenu) + // MARK: Delete Menu - // ------ MENU ----- + private func buildDeleteMenu(metadata: tableMetadata, deleteMenu: inout [UIMenuElement]) { + let deleteConfirmLocal = makeDeleteLocalAction(metadata: metadata) + let deleteConfirmFile = makeDeleteFileAction(metadata: metadata) - if self.networking.isOnline { - let hasLivePhoto = self.database.getMetadataLivePhoto(metadata: metadata) != nil - let isMediaViewController = viewController is NCMedia + let deleteSubMenu = UIMenu( + title: NSLocalizedString("_delete_", comment: ""), + image: utility.loadImage(named: "trash"), + options: .destructive, + children: [deleteConfirmLocal, deleteConfirmFile] + ) + + if metadata.directory { + if !metadata.isDirectoryE2EE && !metadata.e2eEncrypted { + deleteMenu.append(deleteSubMenu) + } + } else { + if !metadata.lock { + deleteMenu.append(deleteSubMenu) + } + } + } - if metadata.directory { - if !metadata.isDirectoryE2EE && !metadata.e2eEncrypted { - deleteMenu.append(deleteSubMenu) + private func makeDeleteFileAction(metadata: tableMetadata) -> UIAction { + return UIAction( + title: NSLocalizedString( + metadata.directory ? "_delete_folder_" : "_delete_file_", + comment: "" + ), + image: utility.loadImage(named: "trash"), + attributes: .destructive + ) { _ in + if let viewController = self.viewController as? NCCollectionViewCommon { + Task { + await self.networking.setStatusWaitDelete( + metadatas: [metadata], + sceneIdentifier: self.sceneIdentifier + ) + await viewController.reloadDataSource() } - } else { - if hasLivePhoto { - mainActionsMenu.append(livePhotoSave) + } else if let viewController = self.viewController as? NCMedia { + Task { + await viewController.deleteImage(with: metadata.ocId) } + } + } + } - if !metadata.lock { - if isMediaViewController { - mainActionsMenu.append(viewInFolder) - } - - if metadata.isModifiableWithQuickLook { - mainActionsMenu.append(modify) - } + private func makeDeleteLocalAction(metadata: tableMetadata) -> UIAction { + return UIAction( + title: NSLocalizedString("_remove_local_file_", comment: ""), + image: utility.loadImage(named: "trash"), + attributes: .destructive + ) { _ in + Task { + let error = await self.networking.deleteCache( + metadata, + sceneIdentifier: self.sceneIdentifier + ) - deleteMenu.append(deleteSubMenu) + await self.networking.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferChange( + status: NCGlobal.shared.networkingStatusDelete, + account: metadata.account, + fileName: metadata.fileName, + serverUrl: metadata.serverUrl, + selector: metadata.sessionSelector, + ocId: metadata.ocId, + destination: nil, + error: error + ) } } - - let baseChildren = [ - UIMenu(title: "", options: .displayAsPalette, children: shortMenu), - UIMenu(title: "", options: .displayInline, children: mainActionsMenu), - UIMenu(title: "", options: .displayInline, children: clientIntegrationMenu), - UIMenu(title: "", options: .displayInline, children: deleteMenu) - ] - - let finalMenu = UIMenu(title: "", children: (metadata.lock ? [detail] : [detail, share, favorite]) + baseChildren) - finalMenu.preferredElementSize = .medium - - return finalMenu - } else { - return UIMenu() } } - // MARK: - Helper Methods + // MARK: Client Integration private func buildClientIntegrationMenuItems(capabilities: NKCapabilities.Capabilities, metadata: tableMetadata, clientIntegrationMenu: inout [UIMenuElement]) { guard let apps = capabilities.clientIntegration?.apps else { return } From 1a1a9033f3382ee395069268a3f9171f993a75ce Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 10 Dec 2025 16:09:28 +0100 Subject: [PATCH 22/63] Refactor Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCContextMenu.swift | 36 ++++++++++++++++-------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/iOSClient/Menu/NCContextMenu.swift b/iOSClient/Menu/NCContextMenu.swift index d7537a5398..ffe969f73a 100644 --- a/iOSClient/Menu/NCContextMenu.swift +++ b/iOSClient/Menu/NCContextMenu.swift @@ -40,10 +40,6 @@ class NCContextMenu: NSObject { return UIMenu() } - var deleteMenu: [UIMenuElement] = [] - var mainActionsMenu: [UIMenuElement] = [] - var clientIntegrationMenu: [UIMenuElement] = [] - let localFile = database.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) let isOffline = localFile?.offline == true @@ -52,20 +48,18 @@ class NCContextMenu: NSObject { let favorite = makeFavoriteAction(metadata: metadata) let share = makeShareAction() - buildMainActionsMenu( + let mainActionsMenu = buildMainActionsMenu( metadata: metadata, capabilities: capabilities, - isOffline: isOffline, - mainActionsMenu: &mainActionsMenu + isOffline: isOffline ) - buildClientIntegrationMenuItems( + let clientIntegrationMenu = buildClientIntegrationMenuItems( capabilities: capabilities, - metadata: metadata, - clientIntegrationMenu: &clientIntegrationMenu + metadata: metadata ) - buildDeleteMenu(metadata: metadata, deleteMenu: &deleteMenu) + let deleteMenu = buildDeleteMenu(metadata: metadata) // Assemble final menu if self.networking.isOnline { @@ -134,9 +128,9 @@ class NCContextMenu: NSObject { private func buildMainActionsMenu( metadata: tableMetadata, capabilities: NKCapabilities.Capabilities, - isOffline: Bool, - mainActionsMenu: inout [UIMenuElement] - ) { + isOffline: Bool + ) -> [UIMenuElement] { + var mainActionsMenu: [UIMenuElement] = [] // Lock/Unlock if NCNetworking.shared.isOnline, !metadata.directory, @@ -195,6 +189,8 @@ class NCContextMenu: NSObject { metadata.directory { mainActionsMenu.append(makeColorFolderAction(metadata: metadata)) } + + return mainActionsMenu } // MARK: E2EE Actions @@ -407,7 +403,8 @@ class NCContextMenu: NSObject { // MARK: Delete Menu - private func buildDeleteMenu(metadata: tableMetadata, deleteMenu: inout [UIMenuElement]) { + private func buildDeleteMenu(metadata: tableMetadata) -> [UIMenuElement] { + var deleteMenu: [UIMenuElement] = [] let deleteConfirmLocal = makeDeleteLocalAction(metadata: metadata) let deleteConfirmFile = makeDeleteFileAction(metadata: metadata) @@ -427,6 +424,8 @@ class NCContextMenu: NSObject { deleteMenu.append(deleteSubMenu) } } + + return deleteMenu } private func makeDeleteFileAction(metadata: tableMetadata) -> UIAction { @@ -484,8 +483,9 @@ class NCContextMenu: NSObject { // MARK: Client Integration - private func buildClientIntegrationMenuItems(capabilities: NKCapabilities.Capabilities, metadata: tableMetadata, clientIntegrationMenu: inout [UIMenuElement]) { - guard let apps = capabilities.clientIntegration?.apps else { return } + private func buildClientIntegrationMenuItems(capabilities: NKCapabilities.Capabilities, metadata: tableMetadata) -> [UIMenuElement] { + var clientIntegrationMenu: [UIMenuElement] = [] + guard let apps = capabilities.clientIntegration?.apps else { return [] } for (_, context) in apps { for item in context.contextMenu { @@ -573,5 +573,7 @@ class NCContextMenu: NSObject { } } } + + return clientIntegrationMenu } } From 471f8ad1e482b5ca7ff5641d0f3905092d07bd58 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 11 Dec 2025 14:48:09 +0100 Subject: [PATCH 23/63] WIP Signed-off-by: Milen Pivchev --- .../NCCollectionViewCommon+CollectionViewDelegate.swift | 2 +- .../Section Header Footer/NCSectionFirstHeader.swift | 2 +- iOSClient/Media/NCMedia+CollectionViewDelegate.swift | 2 +- iOSClient/Menu/NCContextMenu.swift | 4 +--- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift index 816dc1cc87..48a0300800 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift @@ -166,7 +166,7 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { return UIContextMenuConfiguration(identifier: identifier, previewProvider: { return nil }, actionProvider: { _ in - let contextMenu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, image: image, sender: cell) + let contextMenu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, sender: cell) return contextMenu.viewMenu() }) } diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index 63c79ef815..c4ba110984 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -240,7 +240,7 @@ extension NCSectionFirstHeader: UICollectionViewDelegate { 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) + let contextMenu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: viewController, sceneIdentifier: self.sceneIdentifier, sender: cell) return contextMenu.viewMenu() }) #endif diff --git a/iOSClient/Media/NCMedia+CollectionViewDelegate.swift b/iOSClient/Media/NCMedia+CollectionViewDelegate.swift index 8535cad6b4..90da5a609b 100644 --- a/iOSClient/Media/NCMedia+CollectionViewDelegate.swift +++ b/iOSClient/Media/NCMedia+CollectionViewDelegate.swift @@ -45,7 +45,7 @@ extension NCMedia: UICollectionViewDelegate { return NCViewerProviderContextMenu(metadata: metadata, image: image, sceneIdentifier: self.sceneIdentifier) }, actionProvider: { _ in let cell = collectionView.cellForItem(at: indexPath) - let contextMenu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, image: image, sender: collectionView) + let contextMenu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, sender: collectionView) return contextMenu.viewMenu() }) } diff --git a/iOSClient/Menu/NCContextMenu.swift b/iOSClient/Menu/NCContextMenu.swift index ffe969f73a..97d522bfdd 100644 --- a/iOSClient/Menu/NCContextMenu.swift +++ b/iOSClient/Menu/NCContextMenu.swift @@ -21,14 +21,12 @@ class NCContextMenu: NSObject { let metadata: tableMetadata let sceneIdentifier: String let viewController: UIViewController - let image: UIImage? let sender: Any? - init(metadata: tableMetadata, viewController: UIViewController, sceneIdentifier: String, image: UIImage?, sender: Any?) { + init(metadata: tableMetadata, viewController: UIViewController, sceneIdentifier: String, sender: Any?) { self.metadata = metadata self.viewController = viewController self.sceneIdentifier = sceneIdentifier - self.image = image self.sender = sender } From 4821a4238b0a448f9e9814dfa3ce01f8d2779ae6 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 6 Jan 2026 15:26:24 +0100 Subject: [PATCH 24/63] Bring back more button in list This reverts commit c3dee5a51276b3a7fd3b5dc84ce48bbd4f5e6584. Signed-off-by: Milen Pivchev --- Share/NCShareExtension+DataSource.swift | 1 + .../Collection Common/Cell/NCListCell.swift | 29 ++++++++++++++++++- .../Collection Common/Cell/NCListCell.xib | 16 ++++++++-- iOSClient/Select/NCSelect.swift | 1 + 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/Share/NCShareExtension+DataSource.swift b/Share/NCShareExtension+DataSource.swift index f2d6e3e3f6..f6ac57ec99 100644 --- a/Share/NCShareExtension+DataSource.swift +++ b/Share/NCShareExtension+DataSource.swift @@ -91,6 +91,7 @@ extension NCShareExtension: UICollectionViewDataSource { cell.imageLocal.image = nil cell.imageFavorite.image = nil cell.imageShared.image = nil + cell.imageMore.image = nil cell.imageItem.image = nil cell.imageItem.backgroundColor = nil diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 0cbb8c3aad..ee1022e05c 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -35,6 +35,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var labelSubinfo: UILabel! @IBOutlet weak var imageShared: UIImageView! @IBOutlet weak var buttonShared: UIButton! + @IBOutlet weak var imageMore: UIImageView! @IBOutlet weak var buttonMore: UIButton! @IBOutlet weak var separator: UIView! @IBOutlet weak var tag0: UILabel! @@ -97,6 +98,10 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto get { return imageShared } set { imageShared = newValue } } + var fileMoreImage: UIImageView? { + get { return imageMore } + set { imageMore = newValue } + } var cellSeparatorView: UIView? { get { return separator } set { separator = newValue } @@ -141,6 +146,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto labelInfo.text = "" labelSubinfo.text = "" imageShared.image = nil + imageMore.image = nil separatorHeightConstraint.constant = 0.5 tag0.text = "" tag1.text = "" @@ -161,6 +167,10 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto listCellDelegate?.tapShareListItem(with: ocId, ocIdTransfer: ocIdTransfer, sender: sender) } + @IBAction func touchUpInsideMore(_ sender: Any) { +// listCellDelegate?.tapMoreListItem(with: ocId, ocIdTransfer: ocIdTransfer, image: imageItem.image, sender: sender) + } + @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { listCellDelegate?.longPressListItem(with: ocId, ocIdTransfer: ocIdTransfer, gestureRecognizer: gestureRecognizer) } @@ -170,7 +180,12 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto UIAccessibilityCustomAction( name: NSLocalizedString("_share_", comment: ""), target: self, - selector: #selector(touchUpInsideShare(_:)))] + selector: #selector(touchUpInsideShare(_:))), + UIAccessibilityCustomAction( + name: NSLocalizedString("_more_", comment: ""), + target: self, + selector: #selector(touchUpInsideMore(_:))) + ] } func titleInfoTrailingFull() { @@ -181,6 +196,16 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto titleTrailingConstraint.constant = 90 } + func setButtonMore(image: UIImage) { + imageMore.image = image + setA11yActions() + } + + func hideButtonMore(_ status: Bool) { + imageMore.isHidden = status + buttonMore.isHidden = status + } + func hideButtonShare(_ status: Bool) { imageShared.isHidden = status buttonShared.isHidden = status @@ -191,6 +216,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto imageItemLeftConstraint.constant = 45 imageSelect.isHidden = false imageShared.isHidden = true + imageMore.isHidden = true buttonShared.isHidden = true buttonMore.isHidden = true accessibilityCustomActions = nil @@ -198,6 +224,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto imageItemLeftConstraint.constant = 10 imageSelect.isHidden = true imageShared.isHidden = false + imageMore.isHidden = false buttonShared.isHidden = false buttonMore.isHidden = false backgroundView = nil diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.xib b/iOSClient/Main/Collection Common/Cell/NCListCell.xib index 182e4a3ec2..5aea605add 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.xib @@ -80,7 +80,7 @@ - + @@ -107,6 +107,13 @@ + + + + + + + @@ -183,12 +190,14 @@ + + @@ -201,7 +210,7 @@ - + @@ -213,6 +222,7 @@ + diff --git a/iOSClient/Select/NCSelect.swift b/iOSClient/Select/NCSelect.swift index 6dd3d22c32..8a307501cb 100644 --- a/iOSClient/Select/NCSelect.swift +++ b/iOSClient/Select/NCSelect.swift @@ -389,6 +389,7 @@ extension NCSelect: UICollectionViewDataSource { cell.imageLocal.image = nil cell.imageFavorite.image = nil cell.imageShared.image = nil + cell.imageMore.image = nil cell.imageItem.image = nil cell.imageItem.backgroundColor = nil From 93852f62d888b7207c6c3ff990914564951729ed Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 6 Jan 2026 15:55:02 +0100 Subject: [PATCH 25/63] Revert more button in grid Signed-off-by: Milen Pivchev --- .../Collection Common/Cell/NCGridCell.swift | 5 +++++ .../Collection Common/Cell/NCListCell.swift | 1 + .../Cell/NCRecommendationsCell.swift | 3 +-- .../NCCollectionViewCommon.swift | 17 +++++++++++++++++ .../NCSectionFirstHeader.swift | 8 ++++++++ 5 files changed, 32 insertions(+), 2 deletions(-) diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift index cac0e82fed..6d507dfd3a 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift @@ -129,6 +129,10 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto return nil } + @IBAction func touchUpInsideMore(_ sender: Any) { + gridCellDelegate?.tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: imageItem.image, sender: sender) + } + @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { gridCellDelegate?.longPressGridItem(with: ocId, ocIdTransfer: ocIdTransfer, gestureRecognizer: gestureRecognizer) } @@ -191,6 +195,7 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } protocol NCGridCellDelegate: AnyObject { + func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) } diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index ee1022e05c..231089e1bd 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -323,6 +323,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto protocol NCListCellDelegate: AnyObject { func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) + func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) } diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift index c4281d8d92..d7bd73233c 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift @@ -17,6 +17,7 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { @IBOutlet weak var labelFilename: UILabel! @IBOutlet weak var labelInfo: UILabel! + var delegate: NCRecommendationsCellDelegate? var metadata: tableMetadata = tableMetadata() var recommendedFiles: tableRecommendedFiles = tableRecommendedFiles() var id: String = "" @@ -32,8 +33,6 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { } func initCell() { - - image.image = nil labelFilename.text = "" labelInfo.text = "" diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 7e13263e30..96ad518908 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -609,12 +609,25 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - TAP EVENT + func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + } + + func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + } + func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) { guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) } + func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } +// toggleMenu(metadata: metadata, image: image, sender: sender) + } + func tapRichWorkspace(_ sender: Any) { if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { if let viewerRichWorkspace = navigationController.topViewController as? NCViewerRichWorkspace { @@ -628,6 +641,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } + func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { +// toggleMenu(metadata: metadata, image: image, sender: sender) + } + func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { unifiedSearchMore(metadataForSection: metadataForSection) } diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index c4ba110984..43087eaefc 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -9,6 +9,7 @@ import NextcloudKit protocol NCSectionFirstHeaderDelegate: AnyObject { func tapRichWorkspace(_ sender: Any) func tapRecommendations(with metadata: tableMetadata) + func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) } class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegate { @@ -204,6 +205,7 @@ extension NCSectionFirstHeader: UICollectionViewDataSource { cell.labelFilename.text = metadata.fileNameView cell.labelInfo.text = recommendedFiles.reason + cell.delegate = self cell.metadata = metadata cell.recommendedFiles = recommendedFiles cell.id = recommendedFiles.id @@ -254,3 +256,9 @@ extension NCSectionFirstHeader: UICollectionViewDelegateFlowLayout { return CGSize(width: cellHeight, height: cellHeight) } } + +extension NCSectionFirstHeader: NCRecommendationsCellDelegate { + func touchUpInsideButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { + self.delegate?.tapRecommendationsButtonMenu(with: metadata, image: image, sender: sender) + } +} From 54013b7928fdfe3122da245112930776adf507f9 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 6 Jan 2026 15:56:42 +0100 Subject: [PATCH 26/63] WIP This reverts commit 22eae10183eae453eb8a78f68d8d56dbd94e57a9. Signed-off-by: Milen Pivchev --- .../Collection Common/Cell/NCGridCell.swift | 26 ++++++++++++++++++ .../Collection Common/Cell/NCGridCell.xib | 24 +++++++++++++++-- .../Collection Common/Cell/NCPhotoCell.swift | 24 +++++++++++++++++ .../Collection Common/Cell/NCPhotoCell.xib | 27 ++++++++++++++++--- .../Cell/NCRecommendationsCell.swift | 13 +++++++++ .../Cell/NCRecommendationsCell.xib | 26 +++++++++++++++--- 6 files changed, 132 insertions(+), 8 deletions(-) diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift index 6d507dfd3a..582ee2dd6d 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift @@ -32,6 +32,7 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var labelTitle: UILabel! @IBOutlet weak var labelInfo: UILabel! @IBOutlet weak var labelSubinfo: UILabel! + @IBOutlet weak var buttonMore: UIButton! @IBOutlet weak var imageVisualEffect: UIVisualEffectView! @IBOutlet weak var iconsStackView: UIStackView! @@ -137,6 +138,20 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto gridCellDelegate?.longPressGridItem(with: ocId, ocIdTransfer: ocIdTransfer, gestureRecognizer: gestureRecognizer) } + fileprivate func setA11yActions() { + self.accessibilityCustomActions = [ + UIAccessibilityCustomAction( + name: NSLocalizedString("_more_", comment: ""), + target: self, + selector: #selector(touchUpInsideMore(_:))) + ] + } + + func setButtonMore(image: UIImage) { + buttonMore.setImage(image, for: .normal) + setA11yActions() + } + func hideImageItem(_ status: Bool) { imageItem.isHidden = status } @@ -165,7 +180,18 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto labelSubinfo.isHidden = status } + func hideButtonMore(_ status: Bool) { + buttonMore.isHidden = status + } + func selected(_ status: Bool, isEditMode: Bool) { + if isEditMode { + buttonMore.isHidden = true + accessibilityCustomActions = nil + } else { + buttonMore.isHidden = false + setA11yActions() + } if status { imageSelect.isHidden = false imageSelect.image = NCImageCache.shared.getImageCheckedYes() diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.xib b/iOSClient/Main/Collection Common/Cell/NCGridCell.xib index ab009ca843..acf2c9b713 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.xib @@ -1,8 +1,8 @@ - + - + @@ -62,6 +62,19 @@ + @@ -94,6 +107,7 @@ + @@ -110,11 +124,13 @@ + + @@ -131,8 +147,12 @@ + + + + diff --git a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift index c3b172f453..b6be78f651 100644 --- a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift @@ -28,6 +28,7 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt @IBOutlet weak var imageItem: UIImageView! @IBOutlet weak var imageSelect: UIImageView! @IBOutlet weak var imageStatus: UIImageView! + @IBOutlet weak var buttonMore: UIButton! @IBOutlet weak var imageVisualEffect: UIVisualEffectView! var ocId = "" @@ -90,10 +91,32 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt return nil } + @IBAction func touchUpInsideMore(_ sender: Any) { + photoCellDelegate?.tapMorePhotoItem(with: ocId, ocIdTransfer: ocIdTransfer, image: imageItem.image, sender: sender) + } + @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { photoCellDelegate?.longPressPhotoItem(with: ocId, ocIdTransfer: ocIdTransfer, gestureRecognizer: gestureRecognizer) } + fileprivate func setA11yActions() { + self.accessibilityCustomActions = [ + UIAccessibilityCustomAction( + name: NSLocalizedString("_more_", comment: ""), + target: self, + selector: #selector(touchUpInsideMore(_:))) + ] + } + + func setButtonMore(image: UIImage) { + buttonMore.setImage(image, for: .normal) + setA11yActions() + } + + func hideButtonMore(_ status: Bool) { + buttonMore.isHidden = status + } + func hideImageStatus(_ status: Bool) { imageStatus.isHidden = status } @@ -116,5 +139,6 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt } protocol NCPhotoCellDelegate: AnyObject { + func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) func longPressPhotoItem(with objectId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) } diff --git a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.xib b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.xib index 0ffd106c50..bf456974fe 100644 --- a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.xib @@ -1,8 +1,9 @@ - + - + + @@ -42,10 +43,24 @@ + + @@ -55,12 +70,14 @@ + + @@ -70,8 +87,12 @@ + - + + + + diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift index d7bd73233c..d6c8987a05 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift @@ -16,6 +16,7 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { @IBOutlet weak var image: UIImageView! @IBOutlet weak var labelFilename: UILabel! @IBOutlet weak var labelInfo: UILabel! + @IBOutlet weak var buttonMenu: UIButton! var delegate: NCRecommendationsCellDelegate? var metadata: tableMetadata = tableMetadata() @@ -33,6 +34,14 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { } func initCell() { + let imageButton = UIImage(systemName: "ellipsis.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .thin))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [.black, .white])) + + buttonMenu.setImage(imageButton, for: .normal) + buttonMenu.layer.shadowColor = UIColor.black.cgColor + buttonMenu.layer.shadowOpacity = 0.2 + buttonMenu.layer.shadowOffset = CGSize(width: 2, height: 2) + buttonMenu.layer.shadowRadius = 4 + image.image = nil labelFilename.text = "" labelInfo.text = "" @@ -49,4 +58,8 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { image.layer.borderColor = UIColor.clear.cgColor } } + + @IBAction func touchUpInsideButtonMenu(_ sender: Any) { + self.delegate?.touchUpInsideButtonMenu(with: self.metadata, image: image.image, sender: sender) + } } diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.xib b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.xib index 08b058e051..9f5c0a17b3 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.xib @@ -1,8 +1,10 @@ - + - + + + @@ -31,22 +33,39 @@ + + + + @@ -55,8 +74,9 @@ + - + From aed33b18762ed613e6ef876a97ac37aab8c31a3a Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 7 Jan 2026 12:42:09 +0100 Subject: [PATCH 27/63] WIP Signed-off-by: Milen Pivchev --- .../Collection Common/Cell/NCListCell.swift | 62 +++++++++++++++++-- .../NCCollectionViewCommon.swift | 5 ++ iOSClient/Select/NCSelect.swift | 12 +--- 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 231089e1bd..e8a34cec54 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -152,11 +152,51 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto tag1.text = "" titleInfoTrailingDefault() - let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(gestureRecognizer:))) - longPressedGesture.minimumPressDuration = 0.5 - longPressedGesture.delegate = self - longPressedGesture.delaysTouchesBegan = true - self.addGestureRecognizer(longPressedGesture) +// let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(gestureRecognizer:))) +// longPressedGesture.minimumPressDuration = 0.5 +// longPressedGesture.delegate = self +// longPressedGesture.delaysTouchesBegan = true +// longPressedGesture.cancelsTouchesInView = false // Allow button taps to work +// self.addGestureRecognizer(longPressedGesture) + contentView.bringSubviewToFront(buttonMore) + + buttonMore.configuration = .plain() // iOS 15+ only + listCellDelegate?.test(with: ocId, button: buttonMore, sender: self) +// buttonMore.menu = UIMenu(children: [ +// UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { _ in +// print("Edit tapped") +// }, +// UIAction(title: "Delete", image: UIImage(systemName: "trash")) { _ in +// print("Delete tapped") +// }, +// UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { _ in +// print("Edit tapped") +// }, +// UIAction(title: "Delete", image: UIImage(systemName: "trash")) { _ in +// print("Delete tapped") +// }, +// UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { _ in +// print("Edit tapped") +// }, +// UIAction(title: "Delete", image: UIImage(systemName: "trash")) { _ in +// print("Delete tapped") +// }, +// UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { _ in +// print("Edit tapped") +// }, +// UIAction(title: "Delete", image: UIImage(systemName: "trash")) { _ in +// print("Delete tapped") +// }, +// ]) + +// guard let metadata = NCManageDatabase().getMetadataFromOcId(ocId) else { return } +// buttonMore.menu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, sender: cell) + buttonMore.showsMenuAsPrimaryAction = true + + // Debug: verify button is set up correctly + print("Button frame: \(buttonShared.frame)") + print("Button isHidden: \(buttonShared.isHidden)") + print("Button isUserInteractionEnabled: \(buttonShared.isUserInteractionEnabled)") } override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? { @@ -164,7 +204,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } @IBAction func touchUpInsideShare(_ sender: Any) { - listCellDelegate?.tapShareListItem(with: ocId, ocIdTransfer: ocIdTransfer, sender: sender) +// listCellDelegate?.tapShareListItem(with: ocId, ocIdTransfer: ocIdTransfer, sender: sender) } @IBAction func touchUpInsideMore(_ sender: Any) { @@ -174,6 +214,15 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { listCellDelegate?.longPressListItem(with: ocId, ocIdTransfer: ocIdTransfer, gestureRecognizer: gestureRecognizer) } + + // Allow the button to receive taps even with the long press gesture + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + // Don't handle touches on buttons + if touch.view is UIButton { + return false + } + return true + } fileprivate func setA11yActions() { self.accessibilityCustomActions = [ @@ -322,6 +371,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } protocol NCListCellDelegate: AnyObject { + func test(with ocId: String, button: UIButton, sender: Any) func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 96ad518908..ca44d0d9be 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -663,6 +663,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS func longPressMoreGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func test(with ocId: String, button: UIButton, sender: Any) { + guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } + button.menu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, sender: sender).viewMenu() + } + @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) } diff --git a/iOSClient/Select/NCSelect.swift b/iOSClient/Select/NCSelect.swift index 8a307501cb..7120150329 100644 --- a/iOSClient/Select/NCSelect.swift +++ b/iOSClient/Select/NCSelect.swift @@ -29,7 +29,7 @@ protocol NCSelectDelegate: AnyObject { func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], overwrite: Bool, copy: Bool, move: Bool, session: NCSession.Session) } -class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresentationControllerDelegate, NCListCellDelegate, NCSectionFirstHeaderDelegate, NCTransferDelegate { +class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresentationControllerDelegate, NCSectionFirstHeaderDelegate, NCTransferDelegate { @IBOutlet private var collectionView: UICollectionView! @IBOutlet private var buttonCancel: UIBarButtonItem! @IBOutlet private var bottomContraint: NSLayoutConstraint? @@ -262,18 +262,12 @@ class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresent overwrite = sender.isOn } - func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) { } - - func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { } - - func longPressListItem(with odId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func tapRichWorkspace(_ sender: Any) { } func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { } func tapRecommendations(with metadata: tableMetadata) { } - + // MARK: - Push metadata func pushMetadata(_ metadata: tableMetadata) { @@ -376,7 +370,7 @@ extension NCSelect: UICollectionViewDataSource { isShare = metadata.permissions.contains(NCMetadataPermissions.permissionShared) && !metadataFolder.permissions.contains(NCMetadataPermissions.permissionShared) isMounted = metadata.permissions.contains(NCMetadataPermissions.permissionMounted) && !metadataFolder.permissions.contains(NCMetadataPermissions.permissionMounted) - cell.listCellDelegate = self +// cell.listCellDelegate = self cell.fileOcId = metadata.ocId cell.fileOcIdTransfer = metadata.ocIdTransfer From 2c033d198c4434ba80b548596873ffbb3bccfe3b Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 8 Jan 2026 16:52:38 +0100 Subject: [PATCH 28/63] WIP Signed-off-by: Milen Pivchev --- .../Collection Common/Cell/NCGridCell.swift | 24 +++----- .../Collection Common/Cell/NCListCell.swift | 59 ++----------------- .../NCCollectionViewCommon.swift | 51 ++++++++-------- 3 files changed, 36 insertions(+), 98 deletions(-) diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift index 582ee2dd6d..8f7dee2899 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift @@ -36,7 +36,7 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var imageVisualEffect: UIVisualEffectView! @IBOutlet weak var iconsStackView: UIStackView! - var ocId = "" + var ocId = "" { didSet { gridCellDelegate?.tapMoreGridItem(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } var ocIdTransfer = "" var account = "" var user = "" @@ -124,32 +124,23 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto longPressedGesture.delegate = self longPressedGesture.delaysTouchesBegan = true self.addGestureRecognizer(longPressedGesture) + + contentView.bringSubviewToFront(buttonMore) + + buttonMore.menu = nil + buttonMore.showsMenuAsPrimaryAction = true } override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? { return nil } - @IBAction func touchUpInsideMore(_ sender: Any) { - gridCellDelegate?.tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: imageItem.image, sender: sender) - } - @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { gridCellDelegate?.longPressGridItem(with: ocId, ocIdTransfer: ocIdTransfer, gestureRecognizer: gestureRecognizer) } - fileprivate func setA11yActions() { - self.accessibilityCustomActions = [ - UIAccessibilityCustomAction( - name: NSLocalizedString("_more_", comment: ""), - target: self, - selector: #selector(touchUpInsideMore(_:))) - ] - } - func setButtonMore(image: UIImage) { buttonMore.setImage(image, for: .normal) - setA11yActions() } func hideImageItem(_ status: Bool) { @@ -190,7 +181,6 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto accessibilityCustomActions = nil } else { buttonMore.isHidden = false - setA11yActions() } if status { imageSelect.isHidden = false @@ -221,7 +211,7 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } protocol NCGridCellDelegate: AnyObject { - func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) + func tapMoreGridItem(with ocId: String, button: UIButton, sender: Any) func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) } diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index e8a34cec54..861dd1d2cd 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -45,7 +45,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var separatorHeightConstraint: NSLayoutConstraint! @IBOutlet weak var titleTrailingConstraint: NSLayoutConstraint! - var ocId = "" + var ocId = "" { didSet { listCellDelegate?.tapMoreListItem(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } var ocIdTransfer = "" var user = "" @@ -152,51 +152,10 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto tag1.text = "" titleInfoTrailingDefault() -// let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(gestureRecognizer:))) -// longPressedGesture.minimumPressDuration = 0.5 -// longPressedGesture.delegate = self -// longPressedGesture.delaysTouchesBegan = true -// longPressedGesture.cancelsTouchesInView = false // Allow button taps to work -// self.addGestureRecognizer(longPressedGesture) contentView.bringSubviewToFront(buttonMore) - buttonMore.configuration = .plain() // iOS 15+ only - listCellDelegate?.test(with: ocId, button: buttonMore, sender: self) -// buttonMore.menu = UIMenu(children: [ -// UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { _ in -// print("Edit tapped") -// }, -// UIAction(title: "Delete", image: UIImage(systemName: "trash")) { _ in -// print("Delete tapped") -// }, -// UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { _ in -// print("Edit tapped") -// }, -// UIAction(title: "Delete", image: UIImage(systemName: "trash")) { _ in -// print("Delete tapped") -// }, -// UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { _ in -// print("Edit tapped") -// }, -// UIAction(title: "Delete", image: UIImage(systemName: "trash")) { _ in -// print("Delete tapped") -// }, -// UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { _ in -// print("Edit tapped") -// }, -// UIAction(title: "Delete", image: UIImage(systemName: "trash")) { _ in -// print("Delete tapped") -// }, -// ]) - -// guard let metadata = NCManageDatabase().getMetadataFromOcId(ocId) else { return } -// buttonMore.menu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, sender: cell) + buttonMore.menu = nil buttonMore.showsMenuAsPrimaryAction = true - - // Debug: verify button is set up correctly - print("Button frame: \(buttonShared.frame)") - print("Button isHidden: \(buttonShared.isHidden)") - print("Button isUserInteractionEnabled: \(buttonShared.isUserInteractionEnabled)") } override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? { @@ -204,11 +163,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } @IBAction func touchUpInsideShare(_ sender: Any) { -// listCellDelegate?.tapShareListItem(with: ocId, ocIdTransfer: ocIdTransfer, sender: sender) - } - - @IBAction func touchUpInsideMore(_ sender: Any) { -// listCellDelegate?.tapMoreListItem(with: ocId, ocIdTransfer: ocIdTransfer, image: imageItem.image, sender: sender) + listCellDelegate?.tapShareListItem(with: ocId, ocIdTransfer: ocIdTransfer, sender: sender) } @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { @@ -230,10 +185,6 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto name: NSLocalizedString("_share_", comment: ""), target: self, selector: #selector(touchUpInsideShare(_:))), - UIAccessibilityCustomAction( - name: NSLocalizedString("_more_", comment: ""), - target: self, - selector: #selector(touchUpInsideMore(_:))) ] } @@ -371,9 +322,8 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } protocol NCListCellDelegate: AnyObject { - func test(with ocId: String, button: UIButton, sender: Any) + func tapMoreListItem(with ocId: String, button: UIButton, sender: Any) func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) - func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) } @@ -488,3 +438,4 @@ class BidiFilenameLabel: UILabel { return result } } + diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index ca44d0d9be..e86366df1e 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -9,7 +9,7 @@ import NextcloudKit import EasyTipView import LucidBanner -class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionFirstHeaderDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { +class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCPhotoCellDelegate, NCSectionFirstHeaderDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { @IBOutlet weak var collectionView: UICollectionView! @@ -609,25 +609,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - TAP EVENT - func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { - tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) - } - func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) } - func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) { - guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } - - NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) - } - - func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { - guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } -// toggleMenu(metadata: metadata, image: image, sender: sender) - } - func tapRichWorkspace(_ sender: Any) { if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { if let viewerRichWorkspace = navigationController.topViewController as? NCViewerRichWorkspace { @@ -653,21 +638,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS didSelectMetadata(metadata, withOcIds: false) } - func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - - func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func longPressMoreGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - - func test(with ocId: String, button: UIButton, sender: Any) { - guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } - button.menu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, sender: sender).viewMenu() - } - @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) } @@ -1022,3 +996,26 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } } + +extension NCCollectionViewCommon: NCListCellDelegate { + func tapMoreListItem(with ocId: String, button: UIButton, sender: Any) { + guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } + button.menu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, sender: sender).viewMenu() + } + + func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) { + guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } + + NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) + } + + func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) {} +} + +extension NCCollectionViewCommon: NCGridCellDelegate { + func tapMoreGridItem(with ocId: String, button: UIButton, sender: Any) { + tapMoreListItem(with: ocId, button: button, sender: sender) + } + + func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) {} +} From 55161d47ccc1584c8109875b5468a11ca12a7123 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 8 Jan 2026 16:59:11 +0100 Subject: [PATCH 29/63] WIP Signed-off-by: Milen Pivchev --- .../NCCollectionViewCommon.swift | 71 ++++++++++--------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index e86366df1e..722239ab17 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -9,7 +9,7 @@ import NextcloudKit import EasyTipView import LucidBanner -class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCPhotoCellDelegate, NCSectionFirstHeaderDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { +class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { @IBOutlet weak var collectionView: UICollectionView! @@ -609,39 +609,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - TAP EVENT - func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { - tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) - } - - func tapRichWorkspace(_ sender: Any) { - if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { - if let viewerRichWorkspace = navigationController.topViewController as? NCViewerRichWorkspace { - viewerRichWorkspace.richWorkspaceText = richWorkspaceText ?? "" - viewerRichWorkspace.serverUrl = serverUrl - viewerRichWorkspace.delegate = self - - navigationController.modalPresentationStyle = .fullScreen - self.present(navigationController, animated: true, completion: nil) - } - } - } - - func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { -// toggleMenu(metadata: metadata, image: image, sender: sender) - } - - func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { - unifiedSearchMore(metadataForSection: metadataForSection) - } - - func tapRecommendations(with metadata: tableMetadata) { - didSelectMetadata(metadata, withOcIds: false) - } - - func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - - func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) } @@ -1019,3 +986,39 @@ extension NCCollectionViewCommon: NCGridCellDelegate { func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) {} } + +extension NCCollectionViewCommon: NCPhotoCellDelegate { + func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { +// tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + } + + func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } +} + +extension NCCollectionViewCommon: NCSectionFirstHeaderDelegate { + func tapRichWorkspace(_ sender: Any) { + if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { + if let viewerRichWorkspace = navigationController.topViewController as? NCViewerRichWorkspace { + viewerRichWorkspace.richWorkspaceText = richWorkspaceText ?? "" + viewerRichWorkspace.serverUrl = serverUrl + viewerRichWorkspace.delegate = self + + navigationController.modalPresentationStyle = .fullScreen + self.present(navigationController, animated: true, completion: nil) + } + } + } + + func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { +// toggleMenu(metadata: metadata, image: image, sender: sender) + } + + func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { + unifiedSearchMore(metadataForSection: metadataForSection) + } + + func tapRecommendations(with metadata: tableMetadata) { + didSelectMetadata(metadata, withOcIds: false) + } +} + From b8d710cf72d6676da758c8796223ca7a7a53e0ee Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 8 Jan 2026 17:26:18 +0100 Subject: [PATCH 30/63] Refactor Signed-off-by: Milen Pivchev --- ...nViewCommon+CollectionViewDataSource.swift | 3 +- .../NCCollectionViewCommon.swift | 230 +++++++++--------- .../NCSectionFirstHeaderEmptyData.swift | 9 +- 3 files changed, 119 insertions(+), 123 deletions(-) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift index e8080bb079..c3b59d0f22 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift @@ -553,8 +553,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { header.setContent(emptyImage: emptyImage, emptyTitle: emptyTitle, - emptyDescription: emptyDescription, - delegate: self) + emptyDescription: emptyDescription) } else if let header = header as? NCSectionHeader { let text = self.dataSource.getSectionValueLocalization(indexPath: indexPath) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 722239ab17..73b8fc8363 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -9,7 +9,7 @@ import NextcloudKit import EasyTipView import LucidBanner -class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { +class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { @IBOutlet weak var collectionView: UICollectionView! @@ -341,110 +341,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS return true } - // MARK: - Transfer Delegate - - func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } - - func transferChange(status: String, - account: String, - fileName: String, - serverUrl: String, - selector: String?, - ocId: String, - destination: String?, - error: NKError) { - Task { - if error != .success, - error.errorCode != global.errorResourceNotFound { - await showErrorBanner(controller: self.controller, - errorDescription: error.errorDescription, - errorCode: error.errorCode) - } - guard session.account == account else { - return - } - - await self.debouncer.call { - switch status { - // UPLOADED, UPLOADED LIVEPHOTO, DELETE - case self.global.networkingStatusUploaded, - self.global.networkingStatusDelete, - self.global.networkingStatusCopyMove: - if self.isSearchingMode { - self.networkSearch() - } else if self.serverUrl == serverUrl || destination == self.serverUrl { - await self.reloadDataSource() - } - // DOWNLOAD - case self.global.networkingStatusDownloaded: - if serverUrl == self.serverUrl || self.serverUrl.isEmpty { - await self.reloadDataSource() - } - case self.global.networkingStatusDownloadCancel: - if serverUrl == self.serverUrl { - await self.reloadDataSource() - } - // CREATE FOLDER - case self.global.networkingStatusCreateFolder: - if serverUrl == self.serverUrl, - selector != self.global.selectorUploadAutoUpload, - let metadata = await NCManageDatabase.shared.getMetadataAsync(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName)) { - self.pushMetadata(metadata) - } - // RENAME - case self.global.networkingStatusRename: - if self.isSearchingMode { - self.networkSearch() - } else if self.serverUrl == serverUrl { - await self.reloadDataSource() - } - // FAVORITE - case self.global.networkingStatusFavorite: - if self.isSearchingMode { - self.networkSearch() - } else if self is NCFavorite { - await self.reloadDataSource() - } else if self.serverUrl == serverUrl { - await self.reloadDataSource() - } - default: - break - } - } - } - } - - func transferReloadData(serverUrl: String?, requestData: Bool, status: Int?) { - Task { - await self.debouncer.call { - if requestData { - if self.isSearchingMode { - self.networkSearch() - } else if ( self.serverUrl == serverUrl) || serverUrl == nil { - Task { - await self.getServerData() - } - } - } else { - if self.isSearchingMode { - guard status != self.global.metadataStatusWaitDelete, - status != self.global.metadataStatusWaitRename, - status != self.global.metadataStatusWaitMove, - status != self.global.metadataStatusWaitCopy, - status != self.global.metadataStatusWaitFavorite else { - return - } - self.networkSearch() - } else if ( self.serverUrl == serverUrl) || serverUrl == nil { - Task { - await self.reloadDataSource() - } - } - } - } - } - } - // MARK: - NotificationCenter @objc func applicationWillResignActive(_ notification: NSNotification) { @@ -521,8 +417,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS return NCBrandOptions.shared.brand } - func accountSettingsDidDismiss(tblAccount: tableAccount?, controller: NCMainTabBarController?) { } - @MainActor func startGUIGetServerData() { self.dataSource.setGetServerData(false) @@ -802,8 +696,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } completion: { metadatasSearch, error in Task { guard let metadatasSearch, - error == .success, - self.isSearchingMode + error == .success, + self.isSearchingMode else { self.networkSearchInProgress = false await self.reloadDataSource() @@ -962,6 +856,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS return CGSize(width: collectionView.frame.width, height: 0) } } + + // MARK: - Delegates + + func accountSettingsDidDismiss(tblAccount: tableAccount?, controller: NCMainTabBarController?) { } } extension NCCollectionViewCommon: NCListCellDelegate { @@ -989,7 +887,7 @@ extension NCCollectionViewCommon: NCGridCellDelegate { extension NCCollectionViewCommon: NCPhotoCellDelegate { func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { -// tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + // tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) } func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } @@ -1010,15 +908,121 @@ extension NCCollectionViewCommon: NCSectionFirstHeaderDelegate { } func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { -// toggleMenu(metadata: metadata, image: image, sender: sender) + // toggleMenu(metadata: metadata, image: image, sender: sender) } + func tapRecommendations(with metadata: tableMetadata) { + didSelectMetadata(metadata, withOcIds: false) + } +} + +extension NCCollectionViewCommon: NCSectionFooterDelegate { func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { unifiedSearchMore(metadataForSection: metadataForSection) } +} - func tapRecommendations(with metadata: tableMetadata) { - didSelectMetadata(metadata, withOcIds: false) +extension NCCollectionViewCommon: NCTransferDelegate { + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + + func transferChange(status: String, + account: String, + fileName: String, + serverUrl: String, + selector: String?, + ocId: String, + destination: String?, + error: NKError) { + Task { + if error != .success, + error.errorCode != global.errorResourceNotFound { + await showErrorBanner(controller: self.controller, + errorDescription: error.errorDescription, + errorCode: error.errorCode) + } + guard session.account == account else { + return + } + + await self.debouncer.call { + switch status { + // UPLOADED, UPLOADED LIVEPHOTO, DELETE + case self.global.networkingStatusUploaded, + self.global.networkingStatusDelete, + self.global.networkingStatusCopyMove: + if self.isSearchingMode { + self.networkSearch() + } else if self.serverUrl == serverUrl || destination == self.serverUrl { + await self.reloadDataSource() + } + // DOWNLOAD + case self.global.networkingStatusDownloaded: + if serverUrl == self.serverUrl || self.serverUrl.isEmpty { + await self.reloadDataSource() + } + case self.global.networkingStatusDownloadCancel: + if serverUrl == self.serverUrl { + await self.reloadDataSource() + } + // CREATE FOLDER + case self.global.networkingStatusCreateFolder: + if serverUrl == self.serverUrl, + selector != self.global.selectorUploadAutoUpload, + let metadata = await NCManageDatabase.shared.getMetadataAsync(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName)) { + self.pushMetadata(metadata) + } + // RENAME + case self.global.networkingStatusRename: + if self.isSearchingMode { + self.networkSearch() + } else if self.serverUrl == serverUrl { + await self.reloadDataSource() + } + // FAVORITE + case self.global.networkingStatusFavorite: + if self.isSearchingMode { + self.networkSearch() + } else if self is NCFavorite { + await self.reloadDataSource() + } else if self.serverUrl == serverUrl { + await self.reloadDataSource() + } + default: + break + } + } + } + } + + func transferReloadData(serverUrl: String?, requestData: Bool, status: Int?) { + Task { + await self.debouncer.call { + if requestData { + if self.isSearchingMode { + self.networkSearch() + } else if ( self.serverUrl == serverUrl) || serverUrl == nil { + Task { + await self.getServerData() + } + } + } else { + if self.isSearchingMode { + guard status != self.global.metadataStatusWaitDelete, + status != self.global.metadataStatusWaitRename, + status != self.global.metadataStatusWaitMove, + status != self.global.metadataStatusWaitCopy, + status != self.global.metadataStatusWaitFavorite else { + return + } + self.networkSearch() + } else if ( self.serverUrl == serverUrl) || serverUrl == nil { + Task { + await self.reloadDataSource() + } + } + } + } + } } } diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeaderEmptyData.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeaderEmptyData.swift index 64649a793c..009e3a5b40 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeaderEmptyData.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeaderEmptyData.swift @@ -6,16 +6,11 @@ import UIKit import MarkdownKit import RealmSwift -protocol NCSectionFirstHeaderEmptyDataDelegate: AnyObject { -} - class NCSectionFirstHeaderEmptyData: UICollectionReusableView { @IBOutlet weak var emptyImage: UIImageView! @IBOutlet weak var emptyTitle: UILabel! @IBOutlet weak var emptyDescription: UILabel! - weak var delegate: NCSectionFirstHeaderEmptyDataDelegate? - override func awakeFromNib() { super.awakeFromNib() initHeader() @@ -36,9 +31,7 @@ class NCSectionFirstHeaderEmptyData: UICollectionReusableView { func setContent(emptyImage: UIImage?, emptyTitle: String?, - emptyDescription: String?, - delegate: NCSectionFirstHeaderEmptyDataDelegate?) { - self.delegate = delegate + emptyDescription: String?) { self.emptyImage.image = emptyImage self.emptyTitle.text = emptyTitle self.emptyDescription.text = emptyDescription From c4297f02d0824364a92e83978ed5b19c55cab761 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 8 Jan 2026 17:42:39 +0100 Subject: [PATCH 31/63] Recommendations menu Signed-off-by: Milen Pivchev --- .../Cell/NCRecommendationsCell.swift | 13 +++++++------ .../Collection Common/NCCollectionViewCommon.swift | 5 +++-- .../NCSectionFirstHeader.swift | 6 +++--- iOSClient/Select/NCSelect.swift | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift index d6c8987a05..e04a53a42e 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift @@ -9,7 +9,7 @@ import UIKit protocol NCRecommendationsCellDelegate: AnyObject { - func touchUpInsideButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) + func touchUpInsideButtonMenu(with metadata: tableMetadata, button: UIButton, sender: Any) } class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { @@ -21,7 +21,7 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { var delegate: NCRecommendationsCellDelegate? var metadata: tableMetadata = tableMetadata() var recommendedFiles: tableRecommendedFiles = tableRecommendedFiles() - var id: String = "" + var id: String = "" { didSet { delegate?.touchUpInsideButtonMenu(with: metadata, button: buttonMenu, sender: self) /* preconfigure UIMenu with each ocId */ } } override func awakeFromNib() { super.awakeFromNib() @@ -45,6 +45,11 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { image.image = nil labelFilename.text = "" labelInfo.text = "" + + contentView.bringSubviewToFront(buttonMenu) + + buttonMenu.menu = nil + buttonMenu.showsMenuAsPrimaryAction = true } func setImageCorner(withBorder: Bool) { @@ -58,8 +63,4 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { image.layer.borderColor = UIColor.clear.cgColor } } - - @IBAction func touchUpInsideButtonMenu(_ sender: Any) { - self.delegate?.touchUpInsideButtonMenu(with: self.metadata, image: image.image, sender: sender) - } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 73b8fc8363..89a2f0daca 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -887,6 +887,7 @@ extension NCCollectionViewCommon: NCGridCellDelegate { extension NCCollectionViewCommon: NCPhotoCellDelegate { func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { +// tapMoreListItem(with: ocId, button: <#T##UIButton#>, sender: <#T##Any#>) // tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) } @@ -907,8 +908,8 @@ extension NCCollectionViewCommon: NCSectionFirstHeaderDelegate { } } - func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { - // toggleMenu(metadata: metadata, image: image, sender: sender) + func tapRecommendationsButtonMenu(with metadata: tableMetadata, button: UIButton, sender: Any) { + tapMoreListItem(with: metadata.ocId, button: button, sender: sender) } func tapRecommendations(with metadata: tableMetadata) { diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index 43087eaefc..b3cdf6432b 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -9,7 +9,7 @@ import NextcloudKit protocol NCSectionFirstHeaderDelegate: AnyObject { func tapRichWorkspace(_ sender: Any) func tapRecommendations(with metadata: tableMetadata) - func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) + func tapRecommendationsButtonMenu(with metadata: tableMetadata, button: UIButton, sender: Any) } class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegate { @@ -258,7 +258,7 @@ extension NCSectionFirstHeader: UICollectionViewDelegateFlowLayout { } extension NCSectionFirstHeader: NCRecommendationsCellDelegate { - func touchUpInsideButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { - self.delegate?.tapRecommendationsButtonMenu(with: metadata, image: image, sender: sender) + func touchUpInsideButtonMenu(with metadata: tableMetadata, button: UIButton, sender: Any) { + self.delegate?.tapRecommendationsButtonMenu(with: metadata, button: button, sender: sender) } } diff --git a/iOSClient/Select/NCSelect.swift b/iOSClient/Select/NCSelect.swift index 7120150329..5f679f746c 100644 --- a/iOSClient/Select/NCSelect.swift +++ b/iOSClient/Select/NCSelect.swift @@ -264,7 +264,7 @@ class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresent func tapRichWorkspace(_ sender: Any) { } - func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { } + func tapRecommendationsButtonMenu(with metadata: tableMetadata, button: UIButton, sender: Any) {} func tapRecommendations(with metadata: tableMetadata) { } From c11f0754346a4a57f9596f987f525ec03b8566fe Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 8 Jan 2026 17:50:46 +0100 Subject: [PATCH 32/63] Photo cell fix Signed-off-by: Milen Pivchev --- .../Main/Collection Common/Cell/NCPhotoCell.swift | 11 ++++++++--- .../Cell/NCRecommendationsCell.swift | 2 +- .../Collection Common/NCCollectionViewCommon.swift | 5 ++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift index b6be78f651..eca7278d81 100644 --- a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift @@ -31,7 +31,7 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt @IBOutlet weak var buttonMore: UIButton! @IBOutlet weak var imageVisualEffect: UIVisualEffectView! - var ocId = "" + var ocId = "" { didSet { photoCellDelegate?.tapMorePhotoItem(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } var ocIdTransfer = "" var user = "" @@ -85,6 +85,11 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt longPressedGesture.delegate = self longPressedGesture.delaysTouchesBegan = true self.addGestureRecognizer(longPressedGesture) + + contentView.bringSubviewToFront(buttonMore) + + buttonMore.menu = nil + buttonMore.showsMenuAsPrimaryAction = true } override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? { @@ -92,7 +97,7 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt } @IBAction func touchUpInsideMore(_ sender: Any) { - photoCellDelegate?.tapMorePhotoItem(with: ocId, ocIdTransfer: ocIdTransfer, image: imageItem.image, sender: sender) + photoCellDelegate?.tapMorePhotoItem(with: ocId, button: buttonMore, sender: sender) } @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { @@ -139,6 +144,6 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt } protocol NCPhotoCellDelegate: AnyObject { - func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) + func tapMorePhotoItem(with ocId: String, button: UIButton, sender: Any) func longPressPhotoItem(with objectId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) } diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift index e04a53a42e..1e0c0e61a9 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift @@ -21,7 +21,7 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { var delegate: NCRecommendationsCellDelegate? var metadata: tableMetadata = tableMetadata() var recommendedFiles: tableRecommendedFiles = tableRecommendedFiles() - var id: String = "" { didSet { delegate?.touchUpInsideButtonMenu(with: metadata, button: buttonMenu, sender: self) /* preconfigure UIMenu with each ocId */ } } + var id: String = "" { didSet { delegate?.touchUpInsideButtonMenu(with: metadata, button: buttonMenu, sender: self) /* preconfigure UIMenu with each id set */ } } override func awakeFromNib() { super.awakeFromNib() diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 89a2f0daca..87ae993828 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -886,9 +886,8 @@ extension NCCollectionViewCommon: NCGridCellDelegate { } extension NCCollectionViewCommon: NCPhotoCellDelegate { - func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { -// tapMoreListItem(with: ocId, button: <#T##UIButton#>, sender: <#T##Any#>) - // tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + func tapMorePhotoItem(with ocId: String, button: UIButton, sender: Any) { + tapMoreListItem(with: ocId, button: button, sender: sender) } func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } From 8b3e6730fc5c7eb31a60f26469a94189530645e8 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 9 Jan 2026 16:59:14 +0100 Subject: [PATCH 33/63] Resize icons Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCContextMenu.swift | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/iOSClient/Menu/NCContextMenu.swift b/iOSClient/Menu/NCContextMenu.swift index 97d522bfdd..a282cca09d 100644 --- a/iOSClient/Menu/NCContextMenu.swift +++ b/iOSClient/Menu/NCContextMenu.swift @@ -506,25 +506,28 @@ class NCContextMenu: NSObject { if shouldShowMenu { let deferredElement = UIDeferredMenuElement { completion in Task { + func resizedRasterImage(_ image: UIImage, to size: CGSize) -> UIImage { + let format = UIGraphicsImageRendererFormat.default() + format.scale = image.scale + let renderer = UIGraphicsImageRenderer(size: size, format: format) + return renderer.image { _ in + image.draw(in: CGRect(origin: .zero, size: size)) + }.withRenderingMode(image.renderingMode) + } + var iconImage: UIImage if let iconUrl = item.icon, let url = URL(string: metadata.urlBase + iconUrl) { - do { - let (data, _) = try await URLSession.shared.data(from: url) - iconImage = SVGKImage(data: data)?.uiImage.withRenderingMode(.alwaysTemplate) ?? UIImage() - } catch { - iconImage = self.utility.loadImage( - named: "testtube.2", - colors: [NCBrandColor.shared.presentationIconColor] - ) - } + let (data, _) = try await URLSession.shared.data(from: url) + let svgkImage = SVGKImage(data: data)?.uiImage.withRenderingMode(.alwaysTemplate) + iconImage = resizedRasterImage(svgkImage ?? UIImage(), to: .init(width: 23, height: 23)) } else { iconImage = self.utility.loadImage( named: "testtube.2", colors: [NCBrandColor.shared.presentationIconColor] ) } - + let action = await UIAction( title: item.name, image: iconImage From 51e208ae3a54952e0b4fa6655130640cedeaf2ee Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 9 Jan 2026 17:02:05 +0100 Subject: [PATCH 34/63] Refactor Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCContextMenu.swift | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/iOSClient/Menu/NCContextMenu.swift b/iOSClient/Menu/NCContextMenu.swift index a282cca09d..d8b36fbe99 100644 --- a/iOSClient/Menu/NCContextMenu.swift +++ b/iOSClient/Menu/NCContextMenu.swift @@ -514,20 +514,18 @@ class NCContextMenu: NSObject { image.draw(in: CGRect(origin: .zero, size: size)) }.withRenderingMode(image.renderingMode) } - + var iconImage: UIImage + if let iconUrl = item.icon, let url = URL(string: metadata.urlBase + iconUrl) { let (data, _) = try await URLSession.shared.data(from: url) let svgkImage = SVGKImage(data: data)?.uiImage.withRenderingMode(.alwaysTemplate) iconImage = resizedRasterImage(svgkImage ?? UIImage(), to: .init(width: 23, height: 23)) } else { - iconImage = self.utility.loadImage( - named: "testtube.2", - colors: [NCBrandColor.shared.presentationIconColor] - ) + iconImage = UIImage() } - + let action = await UIAction( title: item.name, image: iconImage From e86d9479cb68fb05a4197a9ff0e84fa7b86db7d3 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 13 Jan 2026 12:13:16 +0100 Subject: [PATCH 35/63] remove old code Signed-off-by: Marino Faggiana --- .../NCCollectionViewCommon+CellDelegate.swift | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift new file mode 100644 index 0000000000..62b86d8fd2 --- /dev/null +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift @@ -0,0 +1,8 @@ +// +// Untitled.swift +// Nextcloud +// +// Created by Marino Faggiana on 13/01/26. +// Copyright © 2026 Marino Faggiana. All rights reserved. +// + From 7480d689baaca0c9dc84f05c0c3a813304ede5e3 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 13 Jan 2026 12:13:28 +0100 Subject: [PATCH 36/63] remove old code Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 8 +++-- .../Collection Common/Cell/NCGridCell.swift | 21 +++-------- .../Collection Common/Cell/NCGridCell.xib | 7 ++-- .../Collection Common/Cell/NCListCell.swift | 29 +++++++-------- .../Collection Common/Cell/NCListCell.xib | 5 +-- .../Collection Common/Cell/NCPhotoCell.swift | 36 +++---------------- .../Collection Common/Cell/NCPhotoCell.xib | 12 +++---- .../Cell/NCRecommendationsCell.swift | 4 +-- .../Cell/NCRecommendationsCell.xib | 10 ++---- .../NCCollectionViewCommon+CellDelegate.swift | 21 +++++++---- .../NCCollectionViewCommon.swift | 34 ------------------ .../NCSectionFirstHeader.swift | 11 ++++-- iOSClient/Trash/NCTrash.swift | 2 -- 13 files changed, 62 insertions(+), 138 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 1ea9fa4807..640d3f0913 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -752,6 +752,7 @@ F7C9B9202B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */; }; F7C9B9232B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */; }; F7CADEFD2EA159210057849E /* NCMetadataTranfersSuccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CADEFA2EA1591D0057849E /* NCMetadataTranfersSuccess.swift */; }; + F7CAFE182F164B9500DB35A5 /* NCCollectionViewCommon+CellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE172F164B9200DB35A5 /* NCCollectionViewCommon+CellDelegate.swift */; }; F7CB689A2541676B0050EC94 /* NCMore.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7CB68992541676B0050EC94 /* NCMore.storyboard */; }; F7CBC1232BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */; }; F7CBC1242BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */; }; @@ -1225,7 +1226,6 @@ C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; F310B1EE2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCViewerMedia+VisionKit.swift"; sourceTree = ""; }; - F317C82B2E82E5C200761AEA /* NextcloudKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = NextcloudKit; path = ../NextcloudKit; sourceTree = SOURCE_ROOT; }; F317C82D2E844C5300761AEA /* ClientIntegrationUIViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientIntegrationUIViewer.swift; sourceTree = ""; }; F321DA892B71205A00DDA0E6 /* NCTrashSelectTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCTrashSelectTabBar.swift; sourceTree = ""; }; F32FADA82D1176DE007035E2 /* UIButton+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Extension.swift"; sourceTree = ""; }; @@ -1687,6 +1687,7 @@ F7C9739428F17131002C43E2 /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = ""; }; F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+SecurityGuard.swift"; sourceTree = ""; }; F7CADEFA2EA1591D0057849E /* NCMetadataTranfersSuccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMetadataTranfersSuccess.swift; sourceTree = ""; }; + F7CAFE172F164B9200DB35A5 /* NCCollectionViewCommon+CellDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+CellDelegate.swift"; sourceTree = ""; }; F7CB68992541676B0050EC94 /* NCMore.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCMore.storyboard; sourceTree = ""; }; F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCSectionFirstHeaderEmptyData.xib; sourceTree = ""; }; F7CBC1222BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCSectionFirstHeaderEmptyData.swift; sourceTree = ""; }; @@ -2475,6 +2476,7 @@ F75FE06B2BB01D0D00A0EFEF /* Cell */, F78ACD50219046AC0088454D /* Section Header Footer */, F70D7C3525FFBF81002B9E34 /* NCCollectionViewCommon.swift */, + F7CAFE172F164B9200DB35A5 /* NCCollectionViewCommon+CellDelegate.swift */, F7743A132C33F13A0034F670 /* NCCollectionViewCommon+CollectionViewDataSource.swift */, F74D50342C9855A000BBBF4C /* NCCollectionViewCommon+CollectionViewDataSourcePrefetching.swift */, F7743A112C33F0A20034F670 /* NCCollectionViewCommon+CollectionViewDelegate.swift */, @@ -3227,7 +3229,6 @@ C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */, C0046CDA2A17B98400D87C9D /* NextcloudUITests.xctest */, F7F1FBA62E27D13700C79E20 /* Frameworks */, - F317C82B2E82E5C200761AEA /* NextcloudKit */, ); sourceTree = ""; }; @@ -4732,6 +4733,7 @@ AF4BF614275629E20081CEEF /* NCManageDatabase+Account.swift in Sources */, F76340FA2EBDE9760056F538 /* NCManageDatabaseCore.swift in Sources */, F3E173C02C9B1067006D177A /* AwakeMode.swift in Sources */, + F7CAFE182F164B9500DB35A5 /* NCCollectionViewCommon+CellDelegate.swift in Sources */, F711A4DC2AF92CAE00095DD8 /* NCUtility+Date.swift in Sources */, AF4BF61E27562B3F0081CEEF /* NCManageDatabase+Activity.swift in Sources */, ); @@ -6109,7 +6111,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/nextcloud/NextcloudKit"; requirement = { - branch = declarative_ui; + branch = "declarative-ui"; kind = branch; }; }; diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift index 8f7dee2899..c59ee8de9f 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift @@ -23,6 +23,10 @@ import UIKit +protocol NCGridCellDelegate: AnyObject { + func contextMenu(with ocId: String, button: UIButton, sender: Any) +} + class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProtocol { @IBOutlet weak var imageItem: UIImageView! @IBOutlet weak var imageSelect: UIImageView! @@ -36,7 +40,7 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var imageVisualEffect: UIVisualEffectView! @IBOutlet weak var iconsStackView: UIStackView! - var ocId = "" { didSet { gridCellDelegate?.tapMoreGridItem(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } + var ocId = "" { didSet { gridCellDelegate?.contextMenu(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } var ocIdTransfer = "" var account = "" var user = "" @@ -119,12 +123,6 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto iconsStackView.layer.cornerRadius = 8 iconsStackView.clipsToBounds = true - let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(gestureRecognizer:))) - longPressedGesture.minimumPressDuration = 0.5 - longPressedGesture.delegate = self - longPressedGesture.delaysTouchesBegan = true - self.addGestureRecognizer(longPressedGesture) - contentView.bringSubviewToFront(buttonMore) buttonMore.menu = nil @@ -135,10 +133,6 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto return nil } - @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { - gridCellDelegate?.longPressGridItem(with: ocId, ocIdTransfer: ocIdTransfer, gestureRecognizer: gestureRecognizer) - } - func setButtonMore(image: UIImage) { buttonMore.setImage(image, for: .normal) } @@ -210,11 +204,6 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto func setIconOutlines() {} } -protocol NCGridCellDelegate: AnyObject { - func tapMoreGridItem(with ocId: String, button: UIButton, sender: Any) - func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) -} - // MARK: - Grid Layout class NCGridLayout: UICollectionViewFlowLayout { diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.xib b/iOSClient/Main/Collection Common/Cell/NCGridCell.xib index acf2c9b713..b5cacdc258 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.xib @@ -1,8 +1,8 @@ - + - + @@ -71,9 +71,6 @@ - - - diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 861dd1d2cd..073edc7dc0 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -23,6 +23,11 @@ import UIKit +protocol NCListCellDelegate: AnyObject { + func contextMenu(with ocId: String, button: UIButton, sender: Any) + func tapShareListItem(with ocId: String, button: UIButton, sender: Any) +} + class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProtocol { @IBOutlet weak var imageItem: UIImageView! @IBOutlet weak var imageSelect: UIImageView! @@ -45,7 +50,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var separatorHeightConstraint: NSLayoutConstraint! @IBOutlet weak var titleTrailingConstraint: NSLayoutConstraint! - var ocId = "" { didSet { listCellDelegate?.tapMoreListItem(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } + var ocId = "" { didSet { listCellDelegate?.contextMenu(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } var ocIdTransfer = "" var user = "" @@ -122,6 +127,11 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto override func awakeFromNib() { super.awakeFromNib() + + contentView.bringSubviewToFront(buttonMore) + buttonMore.menu = nil + buttonMore.showsMenuAsPrimaryAction = true + initCell() } @@ -151,11 +161,6 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto tag0.text = "" tag1.text = "" titleInfoTrailingDefault() - - contentView.bringSubviewToFront(buttonMore) - - buttonMore.menu = nil - buttonMore.showsMenuAsPrimaryAction = true } override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? { @@ -163,13 +168,9 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } @IBAction func touchUpInsideShare(_ sender: Any) { - listCellDelegate?.tapShareListItem(with: ocId, ocIdTransfer: ocIdTransfer, sender: sender) + listCellDelegate?.tapShareListItem(with: ocId, button: buttonShared, sender: sender) } - @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { - listCellDelegate?.longPressListItem(with: ocId, ocIdTransfer: ocIdTransfer, gestureRecognizer: gestureRecognizer) - } - // Allow the button to receive taps even with the long press gesture func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { // Don't handle touches on buttons @@ -321,12 +322,6 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } } -protocol NCListCellDelegate: AnyObject { - func tapMoreListItem(with ocId: String, button: UIButton, sender: Any) - func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) - func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) -} - // MARK: - List Layout class NCListLayout: UICollectionViewFlowLayout { diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.xib b/iOSClient/Main/Collection Common/Cell/NCListCell.xib index 5aea605add..be2b91f06c 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.xib @@ -103,11 +103,8 @@ - - - - + diff --git a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift index eca7278d81..0a8eaadd24 100644 --- a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift @@ -23,15 +23,18 @@ import UIKit -class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProtocol { +protocol NCPhotoCellDelegate: AnyObject { + func contextMenu(with ocId: String, button: UIButton, sender: Any) +} +class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProtocol { @IBOutlet weak var imageItem: UIImageView! @IBOutlet weak var imageSelect: UIImageView! @IBOutlet weak var imageStatus: UIImageView! @IBOutlet weak var buttonMore: UIButton! @IBOutlet weak var imageVisualEffect: UIVisualEffectView! - var ocId = "" { didSet { photoCellDelegate?.tapMorePhotoItem(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } + var ocId = "" { didSet { photoCellDelegate?.contextMenu(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } var ocIdTransfer = "" var user = "" @@ -80,12 +83,6 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt imageVisualEffect.clipsToBounds = true imageVisualEffect.alpha = 0.5 - let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(gestureRecognizer:))) - longPressedGesture.minimumPressDuration = 0.5 - longPressedGesture.delegate = self - longPressedGesture.delaysTouchesBegan = true - self.addGestureRecognizer(longPressedGesture) - contentView.bringSubviewToFront(buttonMore) buttonMore.menu = nil @@ -96,26 +93,8 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt return nil } - @IBAction func touchUpInsideMore(_ sender: Any) { - photoCellDelegate?.tapMorePhotoItem(with: ocId, button: buttonMore, sender: sender) - } - - @objc func longPress(gestureRecognizer: UILongPressGestureRecognizer) { - photoCellDelegate?.longPressPhotoItem(with: ocId, ocIdTransfer: ocIdTransfer, gestureRecognizer: gestureRecognizer) - } - - fileprivate func setA11yActions() { - self.accessibilityCustomActions = [ - UIAccessibilityCustomAction( - name: NSLocalizedString("_more_", comment: ""), - target: self, - selector: #selector(touchUpInsideMore(_:))) - ] - } - func setButtonMore(image: UIImage) { buttonMore.setImage(image, for: .normal) - setA11yActions() } func hideButtonMore(_ status: Bool) { @@ -142,8 +121,3 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt accessibilityValue = value } } - -protocol NCPhotoCellDelegate: AnyObject { - func tapMorePhotoItem(with ocId: String, button: UIButton, sender: Any) - func longPressPhotoItem(with objectId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) -} diff --git a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.xib b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.xib index bf456974fe..1e47d12444 100644 --- a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.xib @@ -1,9 +1,8 @@ - + - - + @@ -52,9 +51,6 @@ - - - @@ -89,10 +85,10 @@ - + - + diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift index 1e0c0e61a9..6344abcd0b 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift @@ -9,7 +9,7 @@ import UIKit protocol NCRecommendationsCellDelegate: AnyObject { - func touchUpInsideButtonMenu(with metadata: tableMetadata, button: UIButton, sender: Any) + func contextMenu(with metadata: tableMetadata, button: UIButton, sender: Any) } class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { @@ -21,7 +21,7 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { var delegate: NCRecommendationsCellDelegate? var metadata: tableMetadata = tableMetadata() var recommendedFiles: tableRecommendedFiles = tableRecommendedFiles() - var id: String = "" { didSet { delegate?.touchUpInsideButtonMenu(with: metadata, button: buttonMenu, sender: self) /* preconfigure UIMenu with each id set */ } } + var id: String = "" { didSet { delegate?.contextMenu(with: metadata, button: buttonMenu, sender: self) /* preconfigure UIMenu with each id set */ } } override func awakeFromNib() { super.awakeFromNib() diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.xib b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.xib index 9f5c0a17b3..af6040b9db 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.xib @@ -1,9 +1,8 @@ - + - - + @@ -43,9 +42,6 @@ - - - @@ -76,7 +72,7 @@ - + diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift index 62b86d8fd2..9376fd43c1 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift @@ -1,8 +1,15 @@ -// -// Untitled.swift -// Nextcloud -// -// Created by Marino Faggiana on 13/01/26. -// Copyright © 2026 Marino Faggiana. All rights reserved. -// +extension NCCollectionViewCommon: NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate { + func contextMenu(with ocId: String, button: UIButton, sender: Any) { + Task { + guard let metadata = await self.database.getMetadataFromOcIdAsync(ocId) else { return } + button.menu = NCContextMenu(metadata: metadata, viewController: self, sceneIdentifier: self.sceneIdentifier, sender: sender).viewMenu() + } + } + func tapShareListItem(with ocId: String, button: UIButton, sender: Any) { + Task { + guard let metadata = await self.database.getMetadataFromOcIdAsync(ocId) else { return } + NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) + } + } +} diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 87ae993828..3cf5ea25f1 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -857,42 +857,9 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, } } - // MARK: - Delegates - func accountSettingsDidDismiss(tblAccount: tableAccount?, controller: NCMainTabBarController?) { } } -extension NCCollectionViewCommon: NCListCellDelegate { - func tapMoreListItem(with ocId: String, button: UIButton, sender: Any) { - guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } - button.menu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, sender: sender).viewMenu() - } - - func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) { - guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } - - NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) - } - - func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) {} -} - -extension NCCollectionViewCommon: NCGridCellDelegate { - func tapMoreGridItem(with ocId: String, button: UIButton, sender: Any) { - tapMoreListItem(with: ocId, button: button, sender: sender) - } - - func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) {} -} - -extension NCCollectionViewCommon: NCPhotoCellDelegate { - func tapMorePhotoItem(with ocId: String, button: UIButton, sender: Any) { - tapMoreListItem(with: ocId, button: button, sender: sender) - } - - func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } -} - extension NCCollectionViewCommon: NCSectionFirstHeaderDelegate { func tapRichWorkspace(_ sender: Any) { if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { @@ -908,7 +875,6 @@ extension NCCollectionViewCommon: NCSectionFirstHeaderDelegate { } func tapRecommendationsButtonMenu(with metadata: tableMetadata, button: UIButton, sender: Any) { - tapMoreListItem(with: metadata.ocId, button: button, sender: sender) } func tapRecommendations(with metadata: tableMetadata) { diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index b3cdf6432b..7c4f67bfe6 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -258,7 +258,14 @@ extension NCSectionFirstHeader: UICollectionViewDelegateFlowLayout { } extension NCSectionFirstHeader: NCRecommendationsCellDelegate { - func touchUpInsideButtonMenu(with metadata: tableMetadata, button: UIButton, sender: Any) { - self.delegate?.tapRecommendationsButtonMenu(with: metadata, button: button, sender: sender) + func contextMenu(with metadata: tableMetadata, button: UIButton, sender: Any) { +#if !EXTENSION + Task { + guard let viewController = self.viewController else { + return + } + button.menu = NCContextMenu(metadata: metadata, viewController: viewController, sceneIdentifier: self.sceneIdentifier, sender: sender).viewMenu() + } +#endif } } diff --git a/iOSClient/Trash/NCTrash.swift b/iOSClient/Trash/NCTrash.swift index 797fba1e7c..4a79337bfc 100644 --- a/iOSClient/Trash/NCTrash.swift +++ b/iOSClient/Trash/NCTrash.swift @@ -158,8 +158,6 @@ class NCTrash: UIViewController, NCTrashListCellDelegate, NCTrashGridCellDelegat } } - func longPressGridItem(with objectId: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func longPressMoreGridItem(with objectId: String, gestureRecognizer: UILongPressGestureRecognizer) { } // MARK: - DataSource From a3fe9e11c91ace9999843ac553d410a3e98da9cb Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 13 Jan 2026 12:16:35 +0100 Subject: [PATCH 37/63] cleaning Signed-off-by: Marino Faggiana --- .../Main/Collection Common/NCCollectionViewCommon.swift | 3 --- .../Section Header Footer/NCSectionFirstHeader.swift | 1 - iOSClient/Select/NCSelect.swift | 5 +---- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 3cf5ea25f1..355f299aac 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -874,9 +874,6 @@ extension NCCollectionViewCommon: NCSectionFirstHeaderDelegate { } } - func tapRecommendationsButtonMenu(with metadata: tableMetadata, button: UIButton, sender: Any) { - } - func tapRecommendations(with metadata: tableMetadata) { didSelectMetadata(metadata, withOcIds: false) } diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index 7c4f67bfe6..83d8881831 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -9,7 +9,6 @@ import NextcloudKit protocol NCSectionFirstHeaderDelegate: AnyObject { func tapRichWorkspace(_ sender: Any) func tapRecommendations(with metadata: tableMetadata) - func tapRecommendationsButtonMenu(with metadata: tableMetadata, button: UIButton, sender: Any) } class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegate { diff --git a/iOSClient/Select/NCSelect.swift b/iOSClient/Select/NCSelect.swift index 5f679f746c..b7597981a8 100644 --- a/iOSClient/Select/NCSelect.swift +++ b/iOSClient/Select/NCSelect.swift @@ -263,11 +263,8 @@ class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresent } func tapRichWorkspace(_ sender: Any) { } - - func tapRecommendationsButtonMenu(with metadata: tableMetadata, button: UIButton, sender: Any) {} - func tapRecommendations(with metadata: tableMetadata) { } - + // MARK: - Push metadata func pushMetadata(_ metadata: tableMetadata) { From 3dc900431065f01b09603230bf04ff30a5ba4be8 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 13 Jan 2026 12:54:53 +0100 Subject: [PATCH 38/63] added onMenuIntent Signed-off-by: Marino Faggiana --- .../Collection Common/Cell/NCGridCell.swift | 19 ++++++++-- .../Collection Common/Cell/NCListCell.swift | 36 +++++++++---------- .../Collection Common/Cell/NCPhotoCell.swift | 18 ++++++++-- .../Cell/NCRecommendationsCell.swift | 18 ++++++++-- .../NCCollectionViewCommon+CellDelegate.swift | 4 +++ .../NCSectionFirstHeader.swift | 5 +++ 6 files changed, 76 insertions(+), 24 deletions(-) diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift index c59ee8de9f..4d6e777345 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift @@ -24,6 +24,7 @@ import UIKit protocol NCGridCellDelegate: AnyObject { + func onMenuIntent(with ocId: String) func contextMenu(with ocId: String, button: UIButton, sender: Any) } @@ -90,6 +91,12 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto override func awakeFromNib() { super.awakeFromNib() + + let tapObserver = UITapGestureRecognizer(target: self, action: #selector(handleTapObserver(_:))) + tapObserver.cancelsTouchesInView = false + tapObserver.delegate = self + contentView.addGestureRecognizer(tapObserver) + initCell() } @@ -123,16 +130,24 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto iconsStackView.layer.cornerRadius = 8 iconsStackView.clipsToBounds = true - contentView.bringSubviewToFront(buttonMore) - buttonMore.menu = nil buttonMore.showsMenuAsPrimaryAction = true + + contentView.bringSubviewToFront(buttonMore) } override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? { return nil } + @objc private func handleTapObserver(_ g: UITapGestureRecognizer) { + let location = g.location(in: contentView) + + if buttonMore.frame.contains(location) { + gridCellDelegate?.onMenuIntent(with: ocId) + } + } + func setButtonMore(image: UIImage) { buttonMore.setImage(image, for: .normal) } diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 073edc7dc0..2117359c01 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -24,6 +24,7 @@ import UIKit protocol NCListCellDelegate: AnyObject { + func onMenuIntent(with ocId: String) func contextMenu(with ocId: String, button: UIButton, sender: Any) func tapShareListItem(with ocId: String, button: UIButton, sender: Any) } @@ -128,9 +129,10 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto override func awakeFromNib() { super.awakeFromNib() - contentView.bringSubviewToFront(buttonMore) - buttonMore.menu = nil - buttonMore.showsMenuAsPrimaryAction = true + let tapObserver = UITapGestureRecognizer(target: self, action: #selector(handleTapObserver(_:))) + tapObserver.cancelsTouchesInView = false + tapObserver.delegate = self + contentView.addGestureRecognizer(tapObserver) initCell() } @@ -161,6 +163,10 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto tag0.text = "" tag1.text = "" titleInfoTrailingDefault() + + contentView.bringSubviewToFront(buttonMore) + buttonMore.menu = nil + buttonMore.showsMenuAsPrimaryAction = true } override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? { @@ -171,22 +177,18 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto listCellDelegate?.tapShareListItem(with: ocId, button: buttonShared, sender: sender) } - // Allow the button to receive taps even with the long press gesture - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { - // Don't handle touches on buttons - if touch.view is UIButton { - return false + @objc private func handleTapObserver(_ g: UITapGestureRecognizer) { + let location = g.location(in: contentView) + + if buttonMore.frame.contains(location) { + listCellDelegate?.onMenuIntent(with: ocId) } - return true } - fileprivate func setA11yActions() { - self.accessibilityCustomActions = [ - UIAccessibilityCustomAction( - name: NSLocalizedString("_share_", comment: ""), - target: self, - selector: #selector(touchUpInsideShare(_:))), - ] + // Allow the button to receive taps even with the long press gesture + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + let location = touch.location(in: contentView) + return buttonMore.frame.contains(location) } func titleInfoTrailingFull() { @@ -199,7 +201,6 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto func setButtonMore(image: UIImage) { imageMore.image = image - setA11yActions() } func hideButtonMore(_ status: Bool) { @@ -229,7 +230,6 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto buttonShared.isHidden = false buttonMore.isHidden = false backgroundView = nil - setA11yActions() } if status { var blurEffectView: UIView? diff --git a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift index 0a8eaadd24..8e39d747d4 100644 --- a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift @@ -24,6 +24,7 @@ import UIKit protocol NCPhotoCellDelegate: AnyObject { + func onMenuIntent(with ocId: String) func contextMenu(with ocId: String, button: UIButton, sender: Any) } @@ -63,6 +64,12 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt override func awakeFromNib() { super.awakeFromNib() + + let tapObserver = UITapGestureRecognizer(target: self, action: #selector(handleTapObserver(_:))) + tapObserver.cancelsTouchesInView = false + tapObserver.delegate = self + contentView.addGestureRecognizer(tapObserver) + initCell() } @@ -83,16 +90,23 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt imageVisualEffect.clipsToBounds = true imageVisualEffect.alpha = 0.5 - contentView.bringSubviewToFront(buttonMore) - buttonMore.menu = nil buttonMore.showsMenuAsPrimaryAction = true + contentView.bringSubviewToFront(buttonMore) } override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? { return nil } + @objc private func handleTapObserver(_ g: UITapGestureRecognizer) { + let location = g.location(in: contentView) + + if buttonMore.frame.contains(location) { + photoCellDelegate?.onMenuIntent(with: ocId) + } + } + func setButtonMore(image: UIImage) { buttonMore.setImage(image, for: .normal) } diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift index 6344abcd0b..1f7edfa152 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift @@ -9,6 +9,7 @@ import UIKit protocol NCRecommendationsCellDelegate: AnyObject { + func onMenuIntent(with ocId: String) func contextMenu(with metadata: tableMetadata, button: UIButton, sender: Any) } @@ -25,6 +26,12 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { override func awakeFromNib() { super.awakeFromNib() + + let tapObserver = UITapGestureRecognizer(target: self, action: #selector(handleTapObserver(_:))) + tapObserver.cancelsTouchesInView = false + tapObserver.delegate = self + contentView.addGestureRecognizer(tapObserver) + initCell() } @@ -46,10 +53,17 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { labelFilename.text = "" labelInfo.text = "" - contentView.bringSubviewToFront(buttonMenu) - buttonMenu.menu = nil buttonMenu.showsMenuAsPrimaryAction = true + contentView.bringSubviewToFront(buttonMenu) + } + + @objc private func handleTapObserver(_ g: UITapGestureRecognizer) { + let location = g.location(in: contentView) + + if buttonMenu.frame.contains(location) { + delegate?.onMenuIntent(with: metadata.ocId) + } } func setImageCorner(withBorder: Bool) { diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift index 9376fd43c1..9787e813d7 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift @@ -6,6 +6,10 @@ extension NCCollectionViewCommon: NCListCellDelegate, NCGridCellDelegate, NCPhot } } + func onMenuIntent(with ocId: String) { + print("TAP") + } + func tapShareListItem(with ocId: String, button: UIButton, sender: Any) { Task { guard let metadata = await self.database.getMetadataFromOcIdAsync(ocId) else { return } diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index 83d8881831..7f74b54890 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -265,6 +265,11 @@ extension NCSectionFirstHeader: NCRecommendationsCellDelegate { } button.menu = NCContextMenu(metadata: metadata, viewController: viewController, sceneIdentifier: self.sceneIdentifier, sender: sender).viewMenu() } +#endif + } + + func onMenuIntent(with ocId: String) { +#if !EXTENSION #endif } } From 878fade5ce2a316e705f343d59a443ce5ed43658 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 13 Jan 2026 13:00:53 +0100 Subject: [PATCH 39/63] normalized Signed-off-by: Marino Faggiana --- .../Collection Common/Cell/NCGridCell.swift | 6 ++--- .../Collection Common/Cell/NCListCell.swift | 8 +++---- .../Collection Common/Cell/NCPhotoCell.swift | 6 ++--- .../Cell/NCRecommendationsCell.swift | 22 +++++++++---------- .../Cell/NCRecommendationsCell.xib | 2 +- ...nViewCommon+CollectionViewDataSource.swift | 8 +++---- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift index 4d6e777345..935a0a9360 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift @@ -41,12 +41,12 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var imageVisualEffect: UIVisualEffectView! @IBOutlet weak var iconsStackView: UIStackView! - var ocId = "" { didSet { gridCellDelegate?.contextMenu(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } + var ocId = "" { didSet { delegate?.contextMenu(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } var ocIdTransfer = "" var account = "" var user = "" - weak var gridCellDelegate: NCGridCellDelegate? + weak var delegate: NCGridCellDelegate? var fileOcId: String? { get { return ocId } @@ -144,7 +144,7 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto let location = g.location(in: contentView) if buttonMore.frame.contains(location) { - gridCellDelegate?.onMenuIntent(with: ocId) + delegate?.onMenuIntent(with: ocId) } } diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 2117359c01..23f707e6dd 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -51,11 +51,11 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var separatorHeightConstraint: NSLayoutConstraint! @IBOutlet weak var titleTrailingConstraint: NSLayoutConstraint! - var ocId = "" { didSet { listCellDelegate?.contextMenu(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } + var ocId = "" { didSet { delegate?.contextMenu(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } var ocIdTransfer = "" var user = "" - weak var listCellDelegate: NCListCellDelegate? + weak var delegate: NCListCellDelegate? var fileAvatarImageView: UIImageView? { return imageShared @@ -174,14 +174,14 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } @IBAction func touchUpInsideShare(_ sender: Any) { - listCellDelegate?.tapShareListItem(with: ocId, button: buttonShared, sender: sender) + delegate?.tapShareListItem(with: ocId, button: buttonShared, sender: sender) } @objc private func handleTapObserver(_ g: UITapGestureRecognizer) { let location = g.location(in: contentView) if buttonMore.frame.contains(location) { - listCellDelegate?.onMenuIntent(with: ocId) + delegate?.onMenuIntent(with: ocId) } } diff --git a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift index 8e39d747d4..52ccfb318f 100644 --- a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift @@ -35,11 +35,11 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt @IBOutlet weak var buttonMore: UIButton! @IBOutlet weak var imageVisualEffect: UIVisualEffectView! - var ocId = "" { didSet { photoCellDelegate?.contextMenu(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } + var ocId = "" { didSet { delegate?.contextMenu(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } var ocIdTransfer = "" var user = "" - weak var photoCellDelegate: NCPhotoCellDelegate? + weak var delegate: NCPhotoCellDelegate? var fileOcId: String? { get { return ocId } @@ -103,7 +103,7 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt let location = g.location(in: contentView) if buttonMore.frame.contains(location) { - photoCellDelegate?.onMenuIntent(with: ocId) + delegate?.onMenuIntent(with: ocId) } } diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift index 1f7edfa152..6f0f54a276 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift @@ -17,12 +17,12 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { @IBOutlet weak var image: UIImageView! @IBOutlet weak var labelFilename: UILabel! @IBOutlet weak var labelInfo: UILabel! - @IBOutlet weak var buttonMenu: UIButton! + @IBOutlet weak var buttonMore: UIButton! var delegate: NCRecommendationsCellDelegate? var metadata: tableMetadata = tableMetadata() var recommendedFiles: tableRecommendedFiles = tableRecommendedFiles() - var id: String = "" { didSet { delegate?.contextMenu(with: metadata, button: buttonMenu, sender: self) /* preconfigure UIMenu with each id set */ } } + var id: String = "" { didSet { delegate?.contextMenu(with: metadata, button: buttonMore, sender: self) /* preconfigure UIMenu with each id set */ } } override func awakeFromNib() { super.awakeFromNib() @@ -43,25 +43,25 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { func initCell() { let imageButton = UIImage(systemName: "ellipsis.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .thin))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [.black, .white])) - buttonMenu.setImage(imageButton, for: .normal) - buttonMenu.layer.shadowColor = UIColor.black.cgColor - buttonMenu.layer.shadowOpacity = 0.2 - buttonMenu.layer.shadowOffset = CGSize(width: 2, height: 2) - buttonMenu.layer.shadowRadius = 4 + buttonMore.setImage(imageButton, for: .normal) + buttonMore.layer.shadowColor = UIColor.black.cgColor + buttonMore.layer.shadowOpacity = 0.2 + buttonMore.layer.shadowOffset = CGSize(width: 2, height: 2) + buttonMore.layer.shadowRadius = 4 image.image = nil labelFilename.text = "" labelInfo.text = "" - buttonMenu.menu = nil - buttonMenu.showsMenuAsPrimaryAction = true - contentView.bringSubviewToFront(buttonMenu) + buttonMore.menu = nil + buttonMore.showsMenuAsPrimaryAction = true + contentView.bringSubviewToFront(buttonMore) } @objc private func handleTapObserver(_ g: UITapGestureRecognizer) { let location = g.location(in: contentView) - if buttonMenu.frame.contains(location) { + if buttonMore.frame.contains(location) { delegate?.onMenuIntent(with: metadata.ocId) } } diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.xib b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.xib index af6040b9db..610a832dc1 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.xib @@ -61,7 +61,7 @@ - + diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift index c3b59d0f22..7e437193cd 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift @@ -139,23 +139,23 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if isLayoutPhoto { if metadata.isImageOrVideo { let photoCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as? NCPhotoCell)! - photoCell.photoCellDelegate = self + photoCell.delegate = self cell = photoCell return self.photoCell(cell: photoCell, indexPath: indexPath, metadata: metadata, ext: ext) } else { let gridCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridCell)! - gridCell.gridCellDelegate = self + gridCell.delegate = self cell = gridCell } } else if isLayoutGrid { // LAYOUT GRID let gridCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridCell)! - gridCell.gridCellDelegate = self + gridCell.delegate = self cell = gridCell } else { // LAYOUT LIST let listCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as? NCListCell)! - listCell.listCellDelegate = self + listCell.delegate = self cell = listCell } From 479ec6d44ea554704adaa9736d2b32ee4cb9d039 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 13 Jan 2026 14:56:00 +0100 Subject: [PATCH 40/63] normalized Signed-off-by: Marino Faggiana --- iOSClient/AppDelegate.swift | 2 +- .../NCCollectionViewCommon+CellDelegate.swift | 5 ++++- .../NCCollectionViewCommon.swift | 12 +++++++++-- .../Main/NCMainNavigationController.swift | 8 ++++---- .../Media/NCMedia+TransferDelegate.swift | 10 +++++++++- iOSClient/Networking/NCNetworking+Task.swift | 2 +- .../NCNetworking+TransferDelegate.swift | 10 ++++++++++ .../Networking/NCNetworking+WebDAV.swift | 16 +++++++-------- iOSClient/Networking/NCNetworking.swift | 20 ++----------------- iOSClient/Select/NCSelect.swift | 12 ++++++++++- iOSClient/Share/NCShareNetworking.swift | 6 +++--- iOSClient/Share/NCSharePaging.swift | 2 +- .../NCTermOfServiceModel.swift | 2 +- iOSClient/Transfers/NCTransfersModel.swift | 13 ++++++++++++ .../Viewer/NCViewerMedia/NCViewerMedia.swift | 10 ++++++++++ .../NCViewerMedia/NCViewerMediaPage.swift | 4 ++++ .../NCViewerNextcloudText.swift | 12 ++++++++++- .../Viewer/NCViewerPDF/NCViewerPDF.swift | 10 ++++++++++ .../Viewer/NCViewerProviderContextMenu.swift | 10 ++++++++++ .../NCViewerRichDocument.swift | 12 ++++++++++- 20 files changed, 134 insertions(+), 44 deletions(-) diff --git a/iOSClient/AppDelegate.swift b/iOSClient/AppDelegate.swift index 7fcf867ea8..4d17be87c6 100644 --- a/iOSClient/AppDelegate.swift +++ b/iOSClient/AppDelegate.swift @@ -408,7 +408,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD Task { await NCNetworking.shared.transferDispatcher.notifyAllDelegatesAsync { delegate in try? await Task.sleep(nanoseconds: 500_000_000) - delegate.transferReloadData(serverUrl: nil, requestData: true, status: nil) + delegate.transferReloadDataSource(serverUrl: nil, requestData: true, status: nil) } } } else if let navigationController = UIStoryboard(name: "NCNotification", bundle: nil).instantiateInitialViewController() as? UINavigationController, diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift index 9787e813d7..df6d894686 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift @@ -7,7 +7,10 @@ extension NCCollectionViewCommon: NCListCellDelegate, NCGridCellDelegate, NCPhot } func onMenuIntent(with ocId: String) { - print("TAP") + Task { + // await self.debouncer.pause() + print("TAP") + } } func tapShareListItem(with ocId: String, button: UIButton, sender: Any) { diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 355f299aac..d990fbfced 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -608,7 +608,7 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, fileName: fileName) Task { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: serverUrl, requestData: true, status: nil) + delegate.transferReloadDataSource(serverUrl: serverUrl, requestData: true, status: nil) } } } else { @@ -888,6 +888,14 @@ extension NCCollectionViewCommon: NCSectionFooterDelegate { extension NCCollectionViewCommon: NCTransferDelegate { func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + func transferReloadData() { + Task { + await self.debouncer.call { + self.collectionView.reloadData() + } + } + } + func transferChange(status: String, account: String, fileName: String, @@ -957,7 +965,7 @@ extension NCCollectionViewCommon: NCTransferDelegate { } } - func transferReloadData(serverUrl: String?, requestData: Bool, status: Int?) { + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { Task { await self.debouncer.call { if requestData { diff --git a/iOSClient/Main/NCMainNavigationController.swift b/iOSClient/Main/NCMainNavigationController.swift index 68e2fb8e70..c56942c079 100644 --- a/iOSClient/Main/NCMainNavigationController.swift +++ b/iOSClient/Main/NCMainNavigationController.swift @@ -741,7 +741,7 @@ class NCMainNavigationController: UINavigationController, UINavigationController Task { NCPreferences().setFavoriteOnTop(account: self.session.account, value: !favoriteOnTop) await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: collectionViewCommon.serverUrl, requestData: false, status: nil) + delegate.transferReloadDataSource(serverUrl: collectionViewCommon.serverUrl, requestData: false, status: nil) } await self.updateRightMenu() } @@ -753,7 +753,7 @@ class NCMainNavigationController: UINavigationController, UINavigationController Task { NCPreferences().setDirectoryOnTop(account: self.session.account, value: !directoryOnTop) await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: collectionViewCommon.serverUrl, requestData: false, status: nil) + delegate.transferReloadDataSource(serverUrl: collectionViewCommon.serverUrl, requestData: false, status: nil) } await self.updateRightMenu() } @@ -776,7 +776,7 @@ class NCMainNavigationController: UINavigationController, UINavigationController Task { NCPreferences().setPersonalFilesOnly(account: self.session.account, value: !personalFilesOnly) await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: collectionViewCommon.serverUrl, requestData: false, status: nil) + delegate.transferReloadDataSource(serverUrl: collectionViewCommon.serverUrl, requestData: false, status: nil) } await self.updateRightMenu() } @@ -788,7 +788,7 @@ class NCMainNavigationController: UINavigationController, UINavigationController NCPreferences().showDescription = !showDescriptionKeychain Task { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: collectionViewCommon.serverUrl, requestData: false, status: nil) + delegate.transferReloadDataSource(serverUrl: collectionViewCommon.serverUrl, requestData: false, status: nil) } await self.updateRightMenu() } diff --git a/iOSClient/Media/NCMedia+TransferDelegate.swift b/iOSClient/Media/NCMedia+TransferDelegate.swift index dca0d83f6c..4602e9c053 100644 --- a/iOSClient/Media/NCMedia+TransferDelegate.swift +++ b/iOSClient/Media/NCMedia+TransferDelegate.swift @@ -9,7 +9,9 @@ import NextcloudKit // MARK: - Drag extension NCMedia: NCTransferDelegate { - func transferReloadData(serverUrl: String?, requestData: Bool, status: Int?) { + func transferReloadData() { } + + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { Task { await self.debouncer.call { await self.loadDataSource() @@ -17,6 +19,12 @@ extension NCMedia: NCTransferDelegate { } } + func transferProgressDidUpdate(progress: Float, + totalBytes: Int64, + totalBytesExpected: Int64, + fileName: String, + serverUrl: String) { } + func transferChange(status: String, account: String, fileName: String, diff --git a/iOSClient/Networking/NCNetworking+Task.swift b/iOSClient/Networking/NCNetworking+Task.swift index 2887838214..7680895b5b 100644 --- a/iOSClient/Networking/NCNetworking+Task.swift +++ b/iOSClient/Networking/NCNetworking+Task.swift @@ -110,7 +110,7 @@ extension NCNetworking { await networking.transferDispatcher.notifyAllDelegates { delegate in serverUrls.forEach { serverUrl in - delegate.transferReloadData(serverUrl: serverUrl, requestData: false, status: nil) + delegate.transferReloadDataSource(serverUrl: serverUrl, requestData: false, status: nil) } } } diff --git a/iOSClient/Networking/NCNetworking+TransferDelegate.swift b/iOSClient/Networking/NCNetworking+TransferDelegate.swift index 3a51057f66..3e0eec7b64 100644 --- a/iOSClient/Networking/NCNetworking+TransferDelegate.swift +++ b/iOSClient/Networking/NCNetworking+TransferDelegate.swift @@ -13,6 +13,16 @@ extension NCNetworking: NCTransferDelegate { } } + func transferReloadData() { } + + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } + + func transferProgressDidUpdate(progress: Float, + totalBytes: Int64, + totalBytesExpected: Int64, + fileName: String, + serverUrl: String) { } + func transferChange(status: String, account: String, fileName: String, diff --git a/iOSClient/Networking/NCNetworking+WebDAV.swift b/iOSClient/Networking/NCNetworking+WebDAV.swift index 788624dc23..6742ff5a75 100644 --- a/iOSClient/Networking/NCNetworking+WebDAV.swift +++ b/iOSClient/Networking/NCNetworking+WebDAV.swift @@ -292,7 +292,7 @@ extension NCNetworking { destination: nil, error: error) } others: { delegate in - delegate.transferReloadData(serverUrl: metadata.serverUrl, requestData: false, status: nil) + delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: nil) } } else { await transferDispatcher.notifyAllDelegates { delegate in @@ -355,7 +355,7 @@ extension NCNetworking { await deleteLocalFile(metadata: metadata) await self.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: metadata.serverUrl, requestData: false, status: nil) + delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: nil) } } @@ -454,7 +454,7 @@ extension NCNetworking { status: self.global.metadataStatusWaitDelete) } serverUrls.forEach { serverUrl in - delegate.transferReloadData(serverUrl: serverUrl, requestData: false, status: self.global.metadataStatusWaitDelete) + delegate.transferReloadDataSource(serverUrl: serverUrl, requestData: false, status: self.global.metadataStatusWaitDelete) } } } @@ -533,7 +533,7 @@ extension NCNetworking { Task { await self.transferDispatcher.notifyAllDelegatesAsync { delegate in await NCManageDatabase.shared.renameMetadata(fileNameNew: fileNameNew, ocId: metadata.ocId, status: self.global.metadataStatusWaitRename) - delegate.transferReloadData(serverUrl: metadata.serverUrl, requestData: false, status: self.global.metadataStatusWaitRename) + delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: self.global.metadataStatusWaitRename) } } } @@ -585,7 +585,7 @@ extension NCNetworking { Task { await self.transferDispatcher.notifyAllDelegatesAsync { delegate in await NCManageDatabase.shared.setMetadataCopyMoveAsync(ocId: metadata.ocId, destination: destination, overwrite: overwrite.description, status: self.global.metadataStatusWaitMove) - delegate.transferReloadData(serverUrl: metadata.serverUrl, requestData: false, status: self.global.metadataStatusWaitMove) + delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: self.global.metadataStatusWaitMove) } } } @@ -647,7 +647,7 @@ extension NCNetworking { Task { await self.transferDispatcher.notifyAllDelegatesAsync { delegate in await NCManageDatabase.shared.setMetadataCopyMoveAsync(ocId: metadata.ocId, destination: destination, overwrite: overwrite.description, status: self.global.metadataStatusWaitCopy) - delegate.transferReloadData(serverUrl: metadata.serverUrl, requestData: false, status: self.global.metadataStatusWaitCopy) + delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: self.global.metadataStatusWaitCopy) } } } @@ -707,7 +707,7 @@ extension NCNetworking { Task { await self.transferDispatcher.notifyAllDelegatesAsync { delegate in await NCManageDatabase.shared.setMetadataFavoriteAsync(ocId: metadata.ocId, favorite: !metadata.favorite, saveOldFavorite: metadata.favorite.description, status: self.global.metadataStatusWaitFavorite) - delegate.transferReloadData(serverUrl: metadata.serverUrl, requestData: false, status: self.global.metadataStatusWaitFavorite) + delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: self.global.metadataStatusWaitFavorite) } } } @@ -775,7 +775,7 @@ extension NCNetworking { Task { await self.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: metadata.serverUrl, requestData: false, status: nil) + delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: nil) } } } diff --git a/iOSClient/Networking/NCNetworking.swift b/iOSClient/Networking/NCNetworking.swift index 3f6cffe649..d739adc872 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -28,7 +28,8 @@ protocol NCTransferDelegate: AnyObject { ocId: String, destination: String?, error: NKError) - func transferReloadData(serverUrl: String?, requestData: Bool, status: Int?) + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) + func transferReloadData() func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, @@ -36,23 +37,6 @@ protocol NCTransferDelegate: AnyObject { serverUrl: String) } -extension NCTransferDelegate { - func transferChange(status: String, - account: String, - fileName: String, - serverUrl: String, - selector: String?, - ocId: String, - destination: String?, - error: NKError) {} - func transferReloadData(serverUrl: String?, requestData: Bool, status: Int?) {} - func transferProgressDidUpdate(progress: Float, - totalBytes: Int64, - totalBytesExpected: Int64, - fileName: String, - serverUrl: String) {} -} - /// Actor-based delegate dispatcher using weak references. actor NCTransferDelegateDispatcher { // Weak reference collection of delegates diff --git a/iOSClient/Select/NCSelect.swift b/iOSClient/Select/NCSelect.swift index b7597981a8..380273422b 100644 --- a/iOSClient/Select/NCSelect.swift +++ b/iOSClient/Select/NCSelect.swift @@ -202,7 +202,17 @@ class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresent // Dismission } - // MARK: - NotificationCenter + // MARK: - + + func transferReloadData() { } + + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } + + func transferProgressDidUpdate(progress: Float, + totalBytes: Int64, + totalBytesExpected: Int64, + fileName: String, + serverUrl: String) { } func transferChange(status: String, account: String, diff --git a/iOSClient/Share/NCShareNetworking.swift b/iOSClient/Share/NCShareNetworking.swift index 66614859a0..71f459fc97 100644 --- a/iOSClient/Share/NCShareNetworking.swift +++ b/iOSClient/Share/NCShareNetworking.swift @@ -157,7 +157,7 @@ class NCShareNetworking: NSObject { Task { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: self.metadata.serverUrl, requestData: true, status: nil) + delegate.transferReloadDataSource(serverUrl: self.metadata.serverUrl, requestData: true, status: nil) } } } else { @@ -186,7 +186,7 @@ class NCShareNetworking: NSObject { Task { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: self.metadata.serverUrl, requestData: true, status: nil) + delegate.transferReloadDataSource(serverUrl: self.metadata.serverUrl, requestData: true, status: nil) } } } else { @@ -227,7 +227,7 @@ class NCShareNetworking: NSObject { Task { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: self.metadata.serverUrl, requestData: true, status: nil) + delegate.transferReloadDataSource(serverUrl: self.metadata.serverUrl, requestData: true, status: nil) } } } else { diff --git a/iOSClient/Share/NCSharePaging.swift b/iOSClient/Share/NCSharePaging.swift index fe044d7dad..87dc37e449 100644 --- a/iOSClient/Share/NCSharePaging.swift +++ b/iOSClient/Share/NCSharePaging.swift @@ -126,7 +126,7 @@ class NCSharePaging: UIViewController { super.viewWillDisappear(animated) Task { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: metadata.serverUrl, requestData: false, status: nil) + delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: nil) } } } diff --git a/iOSClient/Terms of service/NCTermOfServiceModel.swift b/iOSClient/Terms of service/NCTermOfServiceModel.swift index 25b3bb9d32..0f1c1e82cd 100644 --- a/iOSClient/Terms of service/NCTermOfServiceModel.swift +++ b/iOSClient/Terms of service/NCTermOfServiceModel.swift @@ -55,7 +55,7 @@ class NCTermOfServiceModel: ObservableObject { if let error { if error == .success { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: nil, requestData: true, status: nil) + delegate.transferReloadDataSource(serverUrl: nil, requestData: true, status: nil) } } else { NCContentPresenter().showError(error: error) diff --git a/iOSClient/Transfers/NCTransfersModel.swift b/iOSClient/Transfers/NCTransfersModel.swift index a4fd4edbc2..4d5730d249 100644 --- a/iOSClient/Transfers/NCTransfersModel.swift +++ b/iOSClient/Transfers/NCTransfersModel.swift @@ -173,6 +173,19 @@ final class TransfersViewModel: ObservableObject, NCMetadataTransfersSuccessDele } extension TransfersViewModel: NCTransferDelegate { + func transferReloadData() { } + + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } + + func transferChange(status: String, + account: String, + fileName: String, + serverUrl: String, + selector: String?, + ocId: String, + destination: String?, + error: NKError) { } + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { Task { @MainActor in let key = "\(serverUrl)|\(fileName)" diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift index 0b2d267e41..68625c3f14 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift @@ -614,6 +614,16 @@ extension NCViewerMedia: EasyTipViewDelegate { } extension NCViewerMedia: NCTransferDelegate { + func transferReloadData() { } + + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } + + func transferProgressDidUpdate(progress: Float, + totalBytes: Int64, + totalBytesExpected: Int64, + fileName: String, + serverUrl: String) { } + func transferChange(status: String, account: String, fileName: String, diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index 48bc45de63..50a2e5ec32 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -569,6 +569,10 @@ extension NCViewerMediaPage: UIScrollViewDelegate { } extension NCViewerMediaPage: NCTransferDelegate { + func transferReloadData() { } + + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } + func transferChange(status: String, account: String, fileName: String, diff --git a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift b/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift index 09e22c0e33..57a9412106 100644 --- a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift +++ b/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift @@ -219,7 +219,7 @@ extension NCViewerNextcloudText: UINavigationControllerDelegate { Task { if parent == nil { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: self.metadata.serverUrl, requestData: true, status: nil) + delegate.transferReloadDataSource(serverUrl: self.metadata.serverUrl, requestData: true, status: nil) } } } @@ -227,6 +227,16 @@ extension NCViewerNextcloudText: UINavigationControllerDelegate { } extension NCViewerNextcloudText: NCTransferDelegate { + func transferReloadData() { } + + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } + + func transferProgressDidUpdate(progress: Float, + totalBytes: Int64, + totalBytesExpected: Int64, + fileName: String, + serverUrl: String) { } + func transferChange(status: String, account: String, fileName: String, diff --git a/iOSClient/Viewer/NCViewerPDF/NCViewerPDF.swift b/iOSClient/Viewer/NCViewerPDF/NCViewerPDF.swift index f602e3dc07..b91d2ada40 100644 --- a/iOSClient/Viewer/NCViewerPDF/NCViewerPDF.swift +++ b/iOSClient/Viewer/NCViewerPDF/NCViewerPDF.swift @@ -514,6 +514,16 @@ extension NCViewerPDF: EasyTipViewDelegate { } extension NCViewerPDF: NCTransferDelegate { + func transferReloadData() { } + + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } + + func transferProgressDidUpdate(progress: Float, + totalBytes: Int64, + totalBytesExpected: Int64, + fileName: String, + serverUrl: String) { } + func transferChange(status: String, account: String, fileName: String, diff --git a/iOSClient/Viewer/NCViewerProviderContextMenu.swift b/iOSClient/Viewer/NCViewerProviderContextMenu.swift index 29e4db791a..316117949d 100644 --- a/iOSClient/Viewer/NCViewerProviderContextMenu.swift +++ b/iOSClient/Viewer/NCViewerProviderContextMenu.swift @@ -269,6 +269,16 @@ extension NCViewerProviderContextMenu: VLCMediaPlayerDelegate { } extension NCViewerProviderContextMenu: NCTransferDelegate { + func transferReloadData() { } + + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } + + func transferProgressDidUpdate(progress: Float, + totalBytes: Int64, + totalBytesExpected: Int64, + fileName: String, + serverUrl: String) { } + func transferChange(status: String, account: String, fileName: String, diff --git a/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift b/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift index 71a61b0cb0..07ae1b9d35 100644 --- a/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift +++ b/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift @@ -385,7 +385,7 @@ extension NCViewerRichDocument: UINavigationControllerDelegate { Task { if parent == nil { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: metadata.serverUrl, requestData: false, status: nil) + delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: nil) } } } @@ -393,6 +393,16 @@ extension NCViewerRichDocument: UINavigationControllerDelegate { } extension NCViewerRichDocument: NCTransferDelegate { + func transferReloadData() { } + + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } + + func transferProgressDidUpdate(progress: Float, + totalBytes: Int64, + totalBytesExpected: Int64, + fileName: String, + serverUrl: String) { } + func transferChange(status: String, account: String, fileName: String, From 047eb4385e2dde924d6d52d8776b5d63a54117cd Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 13 Jan 2026 17:00:21 +0100 Subject: [PATCH 41/63] TransferDelegate Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 2 ++ iOSClient/Media/NCMedia+TransferDelegate.swift | 8 ++------ .../Networking/NCNetworking+TransferDelegate.swift | 10 +++------- iOSClient/Networking/NCNetworking.swift | 2 +- iOSClient/Select/NCSelect.swift | 8 ++------ iOSClient/Transfers/NCTransfersModel.swift | 11 ++--------- iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift | 8 ++------ .../Viewer/NCViewerMedia/NCViewerMediaPage.swift | 2 +- .../NCViewerNextcloudText/NCViewerNextcloudText.swift | 10 +++------- iOSClient/Viewer/NCViewerPDF/NCViewerPDF.swift | 10 +++------- iOSClient/Viewer/NCViewerProviderContextMenu.swift | 10 +++------- .../NCViewerRichdocument/NCViewerRichDocument.swift | 10 +++------- 12 files changed, 27 insertions(+), 64 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 640d3f0913..e82c50d083 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -753,6 +753,7 @@ F7C9B9232B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */; }; F7CADEFD2EA159210057849E /* NCMetadataTranfersSuccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CADEFA2EA1591D0057849E /* NCMetadataTranfersSuccess.swift */; }; F7CAFE182F164B9500DB35A5 /* NCCollectionViewCommon+CellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE172F164B9200DB35A5 /* NCCollectionViewCommon+CellDelegate.swift */; }; + F7CAFE192F168F6000DB35A5 /* NCDebouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A3DB8F2DDE238C008F7EC8 /* NCDebouncer.swift */; }; F7CB689A2541676B0050EC94 /* NCMore.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7CB68992541676B0050EC94 /* NCMore.storyboard */; }; F7CBC1232BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */; }; F7CBC1242BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */; }; @@ -4216,6 +4217,7 @@ F7D4BF312CA2E8D800A5E746 /* TOPasscodeSettingsViewController.m in Sources */, F71916122E2901FB00E13E96 /* NCNetworking+Upload.swift in Sources */, F7D4BF322CA2E8D800A5E746 /* TOPasscodeCircleImage.m in Sources */, + F7CAFE192F168F6000DB35A5 /* NCDebouncer.swift in Sources */, F7D4BF332CA2E8D800A5E746 /* TOPasscodeView.m in Sources */, F77E8C252E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift in Sources */, F7D4BF342CA2E8D800A5E746 /* TOPasscodeCircleButton.m in Sources */, diff --git a/iOSClient/Media/NCMedia+TransferDelegate.swift b/iOSClient/Media/NCMedia+TransferDelegate.swift index 4602e9c053..e35e747684 100644 --- a/iOSClient/Media/NCMedia+TransferDelegate.swift +++ b/iOSClient/Media/NCMedia+TransferDelegate.swift @@ -9,7 +9,7 @@ import NextcloudKit // MARK: - Drag extension NCMedia: NCTransferDelegate { - func transferReloadData() { } + func transferReloadData(serverUrl: String?) { } func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { Task { @@ -19,11 +19,7 @@ extension NCMedia: NCTransferDelegate { } } - func transferProgressDidUpdate(progress: Float, - totalBytes: Int64, - totalBytesExpected: Int64, - fileName: String, - serverUrl: String) { } + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } func transferChange(status: String, account: String, diff --git a/iOSClient/Networking/NCNetworking+TransferDelegate.swift b/iOSClient/Networking/NCNetworking+TransferDelegate.swift index 3e0eec7b64..d3000ffaef 100644 --- a/iOSClient/Networking/NCNetworking+TransferDelegate.swift +++ b/iOSClient/Networking/NCNetworking+TransferDelegate.swift @@ -13,16 +13,12 @@ extension NCNetworking: NCTransferDelegate { } } - func transferReloadData() { } + func transferReloadData(serverUrl: String?) { } func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } - func transferProgressDidUpdate(progress: Float, - totalBytes: Int64, - totalBytesExpected: Int64, - fileName: String, - serverUrl: String) { } - + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + func transferChange(status: String, account: String, fileName: String, diff --git a/iOSClient/Networking/NCNetworking.swift b/iOSClient/Networking/NCNetworking.swift index d739adc872..265363922b 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -29,7 +29,7 @@ protocol NCTransferDelegate: AnyObject { destination: String?, error: NKError) func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) - func transferReloadData() + func transferReloadData(serverUrl: String?) func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, diff --git a/iOSClient/Select/NCSelect.swift b/iOSClient/Select/NCSelect.swift index 380273422b..d28947eb33 100644 --- a/iOSClient/Select/NCSelect.swift +++ b/iOSClient/Select/NCSelect.swift @@ -204,15 +204,11 @@ class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresent // MARK: - - func transferReloadData() { } + func transferReloadData(serverUrl: String?) { } func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } - func transferProgressDidUpdate(progress: Float, - totalBytes: Int64, - totalBytesExpected: Int64, - fileName: String, - serverUrl: String) { } + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } func transferChange(status: String, account: String, diff --git a/iOSClient/Transfers/NCTransfersModel.swift b/iOSClient/Transfers/NCTransfersModel.swift index 4d5730d249..2e1556a59d 100644 --- a/iOSClient/Transfers/NCTransfersModel.swift +++ b/iOSClient/Transfers/NCTransfersModel.swift @@ -173,18 +173,11 @@ final class TransfersViewModel: ObservableObject, NCMetadataTransfersSuccessDele } extension TransfersViewModel: NCTransferDelegate { - func transferReloadData() { } + func transferReloadData(serverUrl: String?) { } func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } - func transferChange(status: String, - account: String, - fileName: String, - serverUrl: String, - selector: String?, - ocId: String, - destination: String?, - error: NKError) { } + func transferChange(status: String, account: String, fileName: String, serverUrl: String, selector: String?, ocId: String, destination: String?, error: NKError) { } func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { Task { @MainActor in diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift index 68625c3f14..af33beaee0 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift @@ -614,15 +614,11 @@ extension NCViewerMedia: EasyTipViewDelegate { } extension NCViewerMedia: NCTransferDelegate { - func transferReloadData() { } + func transferReloadData(serverUrl: String?) { } func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } - func transferProgressDidUpdate(progress: Float, - totalBytes: Int64, - totalBytesExpected: Int64, - fileName: String, - serverUrl: String) { } + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } func transferChange(status: String, account: String, diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index 50a2e5ec32..9dceb20fc1 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -569,7 +569,7 @@ extension NCViewerMediaPage: UIScrollViewDelegate { } extension NCViewerMediaPage: NCTransferDelegate { - func transferReloadData() { } + func transferReloadData(serverUrl: String?) { } func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } diff --git a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift b/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift index 57a9412106..d9326694bc 100644 --- a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift +++ b/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift @@ -227,16 +227,12 @@ extension NCViewerNextcloudText: UINavigationControllerDelegate { } extension NCViewerNextcloudText: NCTransferDelegate { - func transferReloadData() { } + func transferReloadData(serverUrl: String?) { } func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } - func transferProgressDidUpdate(progress: Float, - totalBytes: Int64, - totalBytesExpected: Int64, - fileName: String, - serverUrl: String) { } - + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + func transferChange(status: String, account: String, fileName: String, diff --git a/iOSClient/Viewer/NCViewerPDF/NCViewerPDF.swift b/iOSClient/Viewer/NCViewerPDF/NCViewerPDF.swift index b91d2ada40..01e43d1df1 100644 --- a/iOSClient/Viewer/NCViewerPDF/NCViewerPDF.swift +++ b/iOSClient/Viewer/NCViewerPDF/NCViewerPDF.swift @@ -514,16 +514,12 @@ extension NCViewerPDF: EasyTipViewDelegate { } extension NCViewerPDF: NCTransferDelegate { - func transferReloadData() { } + func transferReloadData(serverUrl: String?) { } func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } - func transferProgressDidUpdate(progress: Float, - totalBytes: Int64, - totalBytesExpected: Int64, - fileName: String, - serverUrl: String) { } - + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + func transferChange(status: String, account: String, fileName: String, diff --git a/iOSClient/Viewer/NCViewerProviderContextMenu.swift b/iOSClient/Viewer/NCViewerProviderContextMenu.swift index 316117949d..db5a3c7841 100644 --- a/iOSClient/Viewer/NCViewerProviderContextMenu.swift +++ b/iOSClient/Viewer/NCViewerProviderContextMenu.swift @@ -269,16 +269,12 @@ extension NCViewerProviderContextMenu: VLCMediaPlayerDelegate { } extension NCViewerProviderContextMenu: NCTransferDelegate { - func transferReloadData() { } + func transferReloadData(serverUrl: String?) { } func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } - func transferProgressDidUpdate(progress: Float, - totalBytes: Int64, - totalBytesExpected: Int64, - fileName: String, - serverUrl: String) { } - + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + func transferChange(status: String, account: String, fileName: String, diff --git a/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift b/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift index 07ae1b9d35..1961ed341a 100644 --- a/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift +++ b/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift @@ -393,16 +393,12 @@ extension NCViewerRichDocument: UINavigationControllerDelegate { } extension NCViewerRichDocument: NCTransferDelegate { - func transferReloadData() { } + func transferReloadData(serverUrl: String?) { } func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { } - func transferProgressDidUpdate(progress: Float, - totalBytes: Int64, - totalBytesExpected: Int64, - fileName: String, - serverUrl: String) { } - + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + func transferChange(status: String, account: String, fileName: String, From 109ec19d0f6d5b3a20308371efa76f29c1859a86 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 13 Jan 2026 17:00:57 +0100 Subject: [PATCH 42/63] transferReloadData Signed-off-by: Marino Faggiana --- .../NCCollectionViewCommon.swift | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index d990fbfced..a774f900d9 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -233,9 +233,12 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, self.sectionFirstHeader?.setRichWorkspaceColor(style: view.traitCollection.userInterfaceStyle) } - NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterChangeTheming), object: nil, queue: .main) { [weak self] _ in - guard let self else { return } - self.collectionView.reloadData() + NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterChangeTheming), object: nil, queue: .main) { _ in + Task { + await NCNetworking.shared.transferDispatcher.notifyAllDelegatesAsync { delegate in + await delegate.transferReloadData(serverUrl: self.serverUrl) + } + } } DispatchQueue.main.async { @@ -420,7 +423,6 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, @MainActor func startGUIGetServerData() { self.dataSource.setGetServerData(false) - self.collectionView.reloadData() // Don't show spinner on iPad root folder if UIDevice.current.userInterfaceIdiom == .pad, @@ -636,11 +638,15 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, } } - UIView.transition(with: self.collectionView, - duration: 0.20, - options: .transitionCrossDissolve, - animations: { self.collectionView.reloadData() }, - completion: nil) + UIView.transition( + with: self.collectionView, + duration: 0.20, + options: .transitionCrossDissolve, + animations: { + self.collectionView.reloadData() + }, + completion: nil + ) await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() } @@ -888,7 +894,10 @@ extension NCCollectionViewCommon: NCSectionFooterDelegate { extension NCCollectionViewCommon: NCTransferDelegate { func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } - func transferReloadData() { + func transferReloadData(serverUrl: String?) { + guard serverUrl == self.serverUrl else { + return + } Task { await self.debouncer.call { self.collectionView.reloadData() @@ -996,4 +1005,3 @@ extension NCCollectionViewCommon: NCTransferDelegate { } } } - From 6b703a2e5c4525b6689327eb6499f1d6cb13749f Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 13 Jan 2026 17:21:09 +0100 Subject: [PATCH 43/63] test Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCNetworking+Recommendations.swift | 5 ++++- iOSClient/Utility/NCDebouncer.swift | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/iOSClient/Networking/NCNetworking+Recommendations.swift b/iOSClient/Networking/NCNetworking+Recommendations.swift index c52e22d688..22ba10e39c 100644 --- a/iOSClient/Networking/NCNetworking+Recommendations.swift +++ b/iOSClient/Networking/NCNetworking+Recommendations.swift @@ -50,7 +50,10 @@ extension NCNetworking { } await NCManageDatabase.shared.createRecommendedFilesAsync(account: session.account, recommendations: recommendationsToInsert) - await collectionView.reloadData() + + await NCNetworking.shared.transferDispatcher.notifyAllDelegatesAsync { delegate in + delegate.transferReloadData(serverUrl: serverUrl) + } } } } diff --git a/iOSClient/Utility/NCDebouncer.swift b/iOSClient/Utility/NCDebouncer.swift index 6055569760..475cedc1e1 100644 --- a/iOSClient/Utility/NCDebouncer.swift +++ b/iOSClient/Utility/NCDebouncer.swift @@ -52,6 +52,10 @@ public actor NCDebouncer { pendingTask = nil } + public func isPausedNow() -> Bool { + return isPaused + } + public func resume() { guard isPaused else { return } From 9fd0a7bf2a18525f2b70ffd0ce7b9c9b04020f53 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 13 Jan 2026 17:52:09 +0100 Subject: [PATCH 44/63] incredible strategy Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 4 ++ iOSClient/AppDelegate.swift | 1 - .../NCCollectionViewCommon+CellDelegate.swift | 3 +- .../NCCollectionViewCommon.swift | 6 +++ iOSClient/NCGlobal.swift | 2 + iOSClient/Utility/NCDebouncer.swift | 20 +++++++--- iOSClient/main.swift | 38 +++++++++++++++++++ 7 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 iOSClient/main.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index e82c50d083..73aa9d92aa 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -754,6 +754,7 @@ F7CADEFD2EA159210057849E /* NCMetadataTranfersSuccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CADEFA2EA1591D0057849E /* NCMetadataTranfersSuccess.swift */; }; F7CAFE182F164B9500DB35A5 /* NCCollectionViewCommon+CellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE172F164B9200DB35A5 /* NCCollectionViewCommon+CellDelegate.swift */; }; F7CAFE192F168F6000DB35A5 /* NCDebouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A3DB8F2DDE238C008F7EC8 /* NCDebouncer.swift */; }; + F7CAFE1B2F16AA8D00DB35A5 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1A2F16AA8600DB35A5 /* main.swift */; }; F7CB689A2541676B0050EC94 /* NCMore.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7CB68992541676B0050EC94 /* NCMore.storyboard */; }; F7CBC1232BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */; }; F7CBC1242BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */; }; @@ -1689,6 +1690,7 @@ F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+SecurityGuard.swift"; sourceTree = ""; }; F7CADEFA2EA1591D0057849E /* NCMetadataTranfersSuccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMetadataTranfersSuccess.swift; sourceTree = ""; }; F7CAFE172F164B9200DB35A5 /* NCCollectionViewCommon+CellDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+CellDelegate.swift"; sourceTree = ""; }; + F7CAFE1A2F16AA8600DB35A5 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; F7CB68992541676B0050EC94 /* NCMore.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCMore.storyboard; sourceTree = ""; }; F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCSectionFirstHeaderEmptyData.xib; sourceTree = ""; }; F7CBC1222BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCSectionFirstHeaderEmptyData.swift; sourceTree = ""; }; @@ -3238,6 +3240,7 @@ children = ( AA517BB42D66149900F8D37C /* .tx */, F702F2CC25EE5B4F008F8E80 /* AppDelegate.swift */, + F7CAFE1A2F16AA8600DB35A5 /* main.swift */, F794E13E2BBC0F70003693D7 /* SceneDelegate.swift */, F7CF067A2E0FF38F0063AD04 /* NCAppStateManager.swift */, F77DD6A72C5CC093009448FB /* NCSession.swift */, @@ -4605,6 +4608,7 @@ F73EF7DF2B02266D0087E6E9 /* NCManageDatabase+Trash.swift in Sources */, F79B646026CA661600838ACA /* UIControl+Extension.swift in Sources */, F768823E2C0DD305001CF441 /* LazyView.swift in Sources */, + F7CAFE1B2F16AA8D00DB35A5 /* main.swift in Sources */, F3E173B02C9AF637006D177A /* ScreenAwakeManager.swift in Sources */, F747EB0D2C4AC1FF00F959A8 /* NCCollectionViewCommon+CollectionViewDelegateFlowLayout.swift in Sources */, F765F73125237E3F00391DBE /* NCRecent.swift in Sources */, diff --git a/iOSClient/AppDelegate.swift b/iOSClient/AppDelegate.swift index 4d17be87c6..226fd9162a 100644 --- a/iOSClient/AppDelegate.swift +++ b/iOSClient/AppDelegate.swift @@ -14,7 +14,6 @@ import EasyTipView import SwiftUI import RealmSwift -@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { var backgroundSessionCompletionHandler: (() -> Void)? var isUiTestingEnabled: Bool { diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift index df6d894686..2d52b6b462 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift @@ -8,8 +8,7 @@ extension NCCollectionViewCommon: NCListCellDelegate, NCGridCellDelegate, NCPhot func onMenuIntent(with ocId: String) { Task { - // await self.debouncer.pause() - print("TAP") + await self.debouncer.pause() } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index a774f900d9..bfce50cd4f 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -241,6 +241,12 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, } } + NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterUserInteractionMonitor), object: nil, queue: .main) { _ in + Task { + await self.debouncer.resume() + } + } + DispatchQueue.main.async { self.collectionView?.collectionViewLayout.invalidateLayout() } diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index e257881739..3edfec9c13 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -284,6 +284,8 @@ final class NCGlobal: Sendable { let notificationCenterPlayerIsPlaying = "playerIsPlaying" let notificationCenterPlayerStoppedPlaying = "playerStoppedPlaying" + let notificationCenterUserInteractionMonitor = "serInteractionMonitor" + // Networking Status let networkingStatusCreateFolder = "statusCreateFolder" let networkingStatusDelete = "statusDelete" diff --git a/iOSClient/Utility/NCDebouncer.swift b/iOSClient/Utility/NCDebouncer.swift index 475cedc1e1..a6307f28b7 100644 --- a/iOSClient/Utility/NCDebouncer.swift +++ b/iOSClient/Utility/NCDebouncer.swift @@ -45,7 +45,9 @@ public actor NCDebouncer { } public func pause() { - guard !isPaused else { return } + guard !isPaused else { + return + } isPaused = true pendingTask?.cancel() @@ -57,7 +59,9 @@ public actor NCDebouncer { } public func resume() { - guard isPaused else { return } + guard isPaused else { + return + } isPaused = false @@ -77,7 +81,9 @@ public actor NCDebouncer { // MARK: - Internal private func scheduleIfNeeded() { - guard pendingTask == nil, !isPaused else { return } + guard pendingTask == nil, !isPaused else { + return + } pendingTask = Task { [weak self] in guard let self else { return } @@ -87,13 +93,17 @@ public actor NCDebouncer { } private func commit() { - guard !isPaused else { return } + guard !isPaused else { + return + } pendingTask?.cancel() pendingTask = nil eventCount = 0 - guard let block = latestBlock else { return } + guard let block = latestBlock else { + return + } latestBlock = nil Task { @MainActor in diff --git a/iOSClient/main.swift b/iOSClient/main.swift new file mode 100644 index 0000000000..e412f98b82 --- /dev/null +++ b/iOSClient/main.swift @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2026 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import UIKit + +UIApplicationMain( + CommandLine.argc, + CommandLine.unsafeArgv, + NSStringFromClass(NCApplication.self), + NSStringFromClass(AppDelegate.self) +) + +final class NCApplication: UIApplication { + override func sendEvent(_ event: UIEvent) { + super.sendEvent(event) + UserInteractionMonitor.shared.handle(event: event) + } +} + +final class UserInteractionMonitor { + static let shared = UserInteractionMonitor() + + private init() {} + + func handle(event: UIEvent) { + guard event.type == .touches else { return } + guard let touches = event.allTouches, !touches.isEmpty else { return } + + let allEnded = touches.allSatisfy { + $0.phase == .ended || $0.phase == .cancelled + } + + if allEnded { + NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUserInteractionMonitor), object: nil, userInfo: nil) + } + } +} From 634b895937f4c227575f6a1e005fedb17bea838e Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 14 Jan 2026 10:16:27 +0100 Subject: [PATCH 45/63] code Signed-off-by: Marino Faggiana --- .../NCCollectionViewCommon+CellDelegate.swift | 2 +- .../NCCollectionViewCommon.swift | 138 +++++++----------- .../NCCollectionViewUnifiedSearch.swift | 17 +-- 3 files changed, 60 insertions(+), 97 deletions(-) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift index 2d52b6b462..f3b1e1da70 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift @@ -8,7 +8,7 @@ extension NCCollectionViewCommon: NCListCellDelegate, NCGridCellDelegate, NCPhot func onMenuIntent(with ocId: String) { Task { - await self.debouncer.pause() + await self.debouncerReloadData.pause() } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index bfce50cd4f..74cfdf2dcd 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -151,7 +151,10 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, return self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session) && capabilities.recommendations } - internal let debouncer = NCDebouncer(maxEventCount: NCBrandOptions.shared.numMaximumProcess) + internal let debouncerReloadDataSource = NCDebouncer(maxEventCount: NCBrandOptions.shared.numMaximumProcess) + internal let debouncerReloadData = NCDebouncer(delay: .seconds(0.5), maxEventCount: NCBrandOptions.shared.numMaximumProcess) + internal let debouncerGetServerData = NCDebouncer(maxEventCount: NCBrandOptions.shared.numMaximumProcess) + internal let debouncerNetworkSearch = NCDebouncer(maxEventCount: NCBrandOptions.shared.numMaximumProcess) // MARK: - View Life Cycle @@ -243,7 +246,7 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterUserInteractionMonitor), object: nil, queue: .main) { _ in Task { - await self.debouncer.resume() + await self.debouncerReloadData.resume() } } @@ -499,6 +502,7 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, self.networking.cancelUnifiedSearchFiles() self.isSearchingMode = false + self.networkSearchInProgress = false self.literalSearch = "" self.providers?.removeAll() self.dataSource.removeAll() @@ -644,15 +648,9 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, } } - UIView.transition( - with: self.collectionView, - duration: 0.20, - options: .transitionCrossDissolve, - animations: { - self.collectionView.reloadData() - }, - completion: nil - ) + await NCNetworking.shared.transferDispatcher.notifyAllDelegatesAsync { delegate in + delegate.transferReloadData(serverUrl: self.serverUrl) + } await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() } @@ -736,7 +734,11 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, guard let metadataForSection = metadataForSection, let lastSearchResult = metadataForSection.lastSearchResult, let cursor = lastSearchResult.cursor, let term = literalSearch else { return } metadataForSection.unifiedSearchInProgress = true - self.collectionView?.reloadData() + Task { + await NCNetworking.shared.transferDispatcher.notifyAllDelegatesAsync { delegate in + delegate.transferReloadData(serverUrl: nil) + } + } self.networking.unifiedSearchFilesProvider(id: lastSearchResult.id, term: term, limit: 5, cursor: cursor, account: session.account) { task in self.searchDataSourceTask = task @@ -758,8 +760,10 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, guard let searchResult = searchResult, let metadatas = metadatas else { return } self.dataSource.appendMetadatasToSection(metadatas, metadataForSection: metadataForSection, lastSearchResult: searchResult) - DispatchQueue.main.async { - self.collectionView?.reloadData() + Task { + await NCNetworking.shared.transferDispatcher.notifyAllDelegatesAsync { delegate in + delegate.transferReloadData(serverUrl: nil) + } } } } @@ -901,11 +905,11 @@ extension NCCollectionViewCommon: NCTransferDelegate { func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } func transferReloadData(serverUrl: String?) { - guard serverUrl == self.serverUrl else { + guard serverUrl == self.serverUrl || serverUrl == nil else { return } Task { - await self.debouncer.call { + await self.debouncerReloadData.call { self.collectionView.reloadData() } } @@ -930,51 +934,22 @@ extension NCCollectionViewCommon: NCTransferDelegate { return } - await self.debouncer.call { - switch status { - // UPLOADED, UPLOADED LIVEPHOTO, DELETE - case self.global.networkingStatusUploaded, - self.global.networkingStatusDelete, - self.global.networkingStatusCopyMove: - if self.isSearchingMode { - self.networkSearch() - } else if self.serverUrl == serverUrl || destination == self.serverUrl { - await self.reloadDataSource() - } - // DOWNLOAD - case self.global.networkingStatusDownloaded: - if serverUrl == self.serverUrl || self.serverUrl.isEmpty { - await self.reloadDataSource() - } - case self.global.networkingStatusDownloadCancel: - if serverUrl == self.serverUrl { - await self.reloadDataSource() - } - // CREATE FOLDER - case self.global.networkingStatusCreateFolder: - if serverUrl == self.serverUrl, - selector != self.global.selectorUploadAutoUpload, - let metadata = await NCManageDatabase.shared.getMetadataAsync(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName)) { - self.pushMetadata(metadata) - } - // RENAME - case self.global.networkingStatusRename: - if self.isSearchingMode { - self.networkSearch() - } else if self.serverUrl == serverUrl { - await self.reloadDataSource() - } - // FAVORITE - case self.global.networkingStatusFavorite: - if self.isSearchingMode { - self.networkSearch() - } else if self is NCFavorite { - await self.reloadDataSource() - } else if self.serverUrl == serverUrl { - await self.reloadDataSource() - } - default: - break + if status == self.global.networkingStatusCreateFolder { + if serverUrl == self.serverUrl, + selector != self.global.selectorUploadAutoUpload, + let metadata = await NCManageDatabase.shared.getMetadataAsync(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName)) { + self.pushMetadata(metadata) + } + return + } + + if self.isSearchingMode { + await self.debouncerNetworkSearch.call { + self.networkSearch() + } + } else if self.serverUrl == serverUrl || destination == self.serverUrl || self.serverUrl.isEmpty { + await self.debouncerReloadDataSource.call { + await self.reloadDataSource() } } } @@ -982,30 +957,23 @@ extension NCCollectionViewCommon: NCTransferDelegate { func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { Task { - await self.debouncer.call { - if requestData { - if self.isSearchingMode { - self.networkSearch() - } else if ( self.serverUrl == serverUrl) || serverUrl == nil { - Task { - await self.getServerData() - } - } - } else { - if self.isSearchingMode { - guard status != self.global.metadataStatusWaitDelete, - status != self.global.metadataStatusWaitRename, - status != self.global.metadataStatusWaitMove, - status != self.global.metadataStatusWaitCopy, - status != self.global.metadataStatusWaitFavorite else { - return - } - self.networkSearch() - } else if ( self.serverUrl == serverUrl) || serverUrl == nil { - Task { - await self.reloadDataSource() - } - } + if self.isSearchingMode { + await self.debouncerNetworkSearch.call { + self.networkSearch() + } + return + } + + if requestData && (self.serverUrl == serverUrl || serverUrl == nil) { + await self.debouncerGetServerData.call { + await self.getServerData() + } + return + } + + if self.serverUrl == serverUrl || serverUrl == nil { + await self.debouncerReloadDataSource.call { + await self.reloadDataSource() } } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewUnifiedSearch.swift b/iOSClient/Main/Collection Common/NCCollectionViewUnifiedSearch.swift index 1bcb3c6317..070f3d5ea4 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewUnifiedSearch.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewUnifiedSearch.swift @@ -19,22 +19,17 @@ class NCCollectionViewUnifiedSearch: ConcurrentOperation, @unchecked Sendable { self.searchResult = searchResult } - func reloadDataThenPerform(_ closure: @escaping (() -> Void)) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - CATransaction.begin() - CATransaction.setCompletionBlock(closure) - self.collectionViewCommon.collectionView.reloadData() - CATransaction.commit() - } - } - override func start() { guard !isCancelled else { return self.finish() } self.collectionViewCommon.dataSource.addSection(metadatas: metadatas, searchResult: searchResult) self.collectionViewCommon.searchResults?.append(self.searchResult) - reloadDataThenPerform { - self.finish() + self.finish() + + Task { + await NCNetworking.shared.transferDispatcher.notifyAllDelegatesAsync { delegate in + delegate.transferReloadData(serverUrl: nil) + } } } } From 29247a925bb9b5c4f21632d00294dcbe2b4acdf4 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 14 Jan 2026 10:37:43 +0100 Subject: [PATCH 46/63] Media Signed-off-by: Marino Faggiana --- iOSClient/Media/NCMedia+TransferDelegate.swift | 13 ++++--------- iOSClient/Media/NCMedia.swift | 3 ++- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/iOSClient/Media/NCMedia+TransferDelegate.swift b/iOSClient/Media/NCMedia+TransferDelegate.swift index e35e747684..439afb8d7a 100644 --- a/iOSClient/Media/NCMedia+TransferDelegate.swift +++ b/iOSClient/Media/NCMedia+TransferDelegate.swift @@ -13,7 +13,7 @@ extension NCMedia: NCTransferDelegate { func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { Task { - await self.debouncer.call { + await self.debouncerLoadDataSource.call { await self.loadDataSource() } } @@ -30,14 +30,9 @@ extension NCMedia: NCTransferDelegate { destination: String?, error: NKError) { Task { - await self.debouncer.call { - switch status { - case self.global.networkingStatusCopyMove: - await self.loadDataSource() - await self.searchMediaUI() - default: - break - } + await self.debouncerSearch.call { + await self.loadDataSource() + await self.searchMediaUI() } } } diff --git a/iOSClient/Media/NCMedia.swift b/iOSClient/Media/NCMedia.swift index 85b2099ee0..a46652401c 100644 --- a/iOSClient/Media/NCMedia.swift +++ b/iOSClient/Media/NCMedia.swift @@ -52,7 +52,8 @@ class NCMedia: UIViewController { var numberOfColumns: Int = 0 var lastNumberOfColumns: Int = 0 - let debouncer = NCDebouncer(maxEventCount: 10) + let debouncerLoadDataSource = NCDebouncer(maxEventCount: 10) + let debouncerSearch = NCDebouncer(maxEventCount: 10) @MainActor var session: NCSession.Session { From fc1825c606d4ceaf4c8e6efb05c8a522d3901636 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 14 Jan 2026 11:01:37 +0100 Subject: [PATCH 47/63] License Signed-off-by: Marino Faggiana --- .../Cell/NCCellProtocol.swift | 25 +++---------------- .../Collection Common/Cell/NCGridCell.swift | 25 +++---------------- .../Collection Common/Cell/NCListCell.swift | 25 +++---------------- .../Collection Common/Cell/NCPhotoCell.swift | 25 +++---------------- .../Cell/NCRecommendationsCell.swift | 10 +++----- .../NCSectionFirstHeader.swift | 13 ++++++++++ 6 files changed, 28 insertions(+), 95 deletions(-) diff --git a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift index b861d54f00..c9ee6c7db1 100644 --- a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift +++ b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift @@ -1,25 +1,6 @@ -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2020 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift index 935a0a9360..abde4ee50a 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift @@ -1,25 +1,6 @@ -// -// NCGridCell.swift -// Nextcloud -// -// Created by Marino Faggiana on 08/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 diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 23f707e6dd..a5a501e4f4 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -1,25 +1,6 @@ -// -// NCListCell.swift -// Nextcloud -// -// Created by Marino Faggiana on 24/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 diff --git a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift index 52ccfb318f..8e04158586 100644 --- a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift @@ -1,25 +1,6 @@ -// -// NCPhotoCell.swift -// Nextcloud -// -// Created by Marino Faggiana on 13/07/2024. -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2024 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift index 6f0f54a276..12b5474056 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift @@ -1,10 +1,6 @@ -// -// NCRecommendationsCell.swift -// Nextcloud -// -// Created by Marino Faggiana on 06/01/25. -// Copyright © 2025 Marino Faggiana. All rights reserved. -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index 7f74b54890..6a562ec8a1 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -123,7 +123,16 @@ 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 @@ -270,6 +279,10 @@ extension NCSectionFirstHeader: NCRecommendationsCellDelegate { func onMenuIntent(with ocId: String) { #if !EXTENSION + Task { + let collectionViewCommon = (self.viewController as? NCCollectionViewCommon) + await collectionViewCommon?.debouncerReloadData.pause() + } #endif } } From 584c3e807cb025ec9f45336cf22f5fdb485f903a Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 14 Jan 2026 11:27:35 +0100 Subject: [PATCH 48/63] add Helper Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 16 ++ .../NCCollectionViewCommon.swift | 2 +- .../E2EE/NCNetworkingE2EEUpload.swift | 2 +- .../Networking/NCNetworking+Helper.swift | 220 ++++++++++++++++++ iOSClient/Networking/NCNetworking.swift | 200 ---------------- iOSClient/Share/NCSharePaging.swift | 2 +- .../NCViewerRichDocument.swift | 4 +- 7 files changed, 241 insertions(+), 205 deletions(-) create mode 100644 iOSClient/Networking/NCNetworking+Helper.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 73aa9d92aa..1a1645500c 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -755,6 +755,13 @@ F7CAFE182F164B9500DB35A5 /* NCCollectionViewCommon+CellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE172F164B9200DB35A5 /* NCCollectionViewCommon+CellDelegate.swift */; }; F7CAFE192F168F6000DB35A5 /* NCDebouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A3DB8F2DDE238C008F7EC8 /* NCDebouncer.swift */; }; F7CAFE1B2F16AA8D00DB35A5 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1A2F16AA8600DB35A5 /* main.swift */; }; + F7CAFE1D2F17A35F00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; + F7CAFE1E2F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; + F7CAFE1F2F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; + F7CAFE202F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; + F7CAFE212F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; + F7CAFE222F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; + F7CAFE232F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; F7CB689A2541676B0050EC94 /* NCMore.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7CB68992541676B0050EC94 /* NCMore.storyboard */; }; F7CBC1232BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */; }; F7CBC1242BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */; }; @@ -1691,6 +1698,7 @@ F7CADEFA2EA1591D0057849E /* NCMetadataTranfersSuccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMetadataTranfersSuccess.swift; sourceTree = ""; }; F7CAFE172F164B9200DB35A5 /* NCCollectionViewCommon+CellDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+CellDelegate.swift"; sourceTree = ""; }; F7CAFE1A2F16AA8600DB35A5 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCNetworking+Helper.swift"; sourceTree = ""; }; F7CB68992541676B0050EC94 /* NCMore.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCMore.storyboard; sourceTree = ""; }; F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCSectionFirstHeaderEmptyData.xib; sourceTree = ""; }; F7CBC1222BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCSectionFirstHeaderEmptyData.swift; sourceTree = ""; }; @@ -2410,6 +2418,7 @@ F72CD63925C19EBF00F46F9A /* NCAutoUpload.swift */, F77BC3EC293E528A005F2B08 /* NCConfigServer.swift */, F75A9EE523796C6F0044CFCE /* NCNetworking.swift */, + F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */, F70898662EDDB39300EF85BD /* NCNetworking+TransferDelegate.swift */, F76341172EBE0BB80056F538 /* NCNetworking+NextcloudKitDelegate.swift */, F7327E1F2B73A42F00A462C7 /* NCNetworking+Download.swift */, @@ -4084,6 +4093,7 @@ F798F0EC2588060A000DAFFD /* UIColor+Extension.swift in Sources */, F76882372C0DD22F001CF441 /* NCPreferences.swift in Sources */, F73EF7E52B02266D0087E6E9 /* NCManageDatabase+Trash.swift in Sources */, + F7CAFE232F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */, F71F6D0D2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */, F763D2A32A249C4500A3C901 /* NCManageDatabase+Capabilities.swift in Sources */, F749B656297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */, @@ -4151,6 +4161,7 @@ F7F1FB9E2E27CE7200C79E20 /* NCNetworking.swift in Sources */, F77DD6AD2C5CC093009448FB /* NCSession.swift in Sources */, F76340F92EBDE9760056F538 /* NCManageDatabaseCore.swift in Sources */, + F7CAFE222F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */, F7E742F42EC0A10C00E2362A /* NCManageDatabase+Account.swift in Sources */, F763410B2EBDFCB10056F538 /* NCManageDatabase+CreateMetadata.swift in Sources */, F7490E6B29882A92009DCE94 /* NCGlobal.swift in Sources */, @@ -4189,6 +4200,7 @@ F74B6D982A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */, F7CF06872E1127460063AD04 /* NCManageDatabase+CreateMetadata.swift in Sources */, F7FDFF722E437E55000D7688 /* NCAccountRequest.swift in Sources */, + F7CAFE202F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */, F7707687263A853700A1BA94 /* NCContentPresenter.swift in Sources */, F343A4B62A1E084200DDA874 /* PHAsset+Extension.swift in Sources */, F70460532499095400BB98A7 /* NotificationCenter+MainThread.swift in Sources */, @@ -4299,6 +4311,7 @@ F78302F928B4C3E600B84583 /* NCManageDatabase+Account.swift in Sources */, F7E0710128B13BB00001B882 /* DashboardData.swift in Sources */, F783030328B4C4DD00B84583 /* ThreadSafeDictionary.swift in Sources */, + F7CAFE1E2F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */, F77ED59128C9CE9D00E24ED0 /* ToolbarData.swift in Sources */, F78302F728B4C3C900B84583 /* NCManageDatabase.swift in Sources */, F7346E1628B0EF5C006CE2D2 /* Widget.swift in Sources */, @@ -4380,6 +4393,7 @@ F3E173C42C9B1067006D177A /* AwakeMode.swift in Sources */, F7D61E932EBF1366007F865B /* UIColor+Extension.swift in Sources */, F76340F42EBDE9760056F538 /* NCManageDatabaseCore.swift in Sources */, + F7CAFE212F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */, F76340EE2EBDE74C0056F538 /* NCManageDatabase.swift in Sources */, F763410A2EBDFCB10056F538 /* NCManageDatabase+CreateMetadata.swift in Sources */, F7E742F72EC0A4CD00E2362A /* NCManageDatabase+LocalFile.swift in Sources */, @@ -4418,6 +4432,7 @@ 370D26AF248A3D7A00121797 /* NCCellProtocol.swift in Sources */, F32FADA92D1176E3007035E2 /* UIButton+Extension.swift in Sources */, F768822C2C0DD1E7001CF441 /* NCPreferences.swift in Sources */, + F7CAFE1D2F17A35F00DB35A5 /* NCNetworking+Helper.swift in Sources */, F71CD6CA2930D7B1006C95C1 /* NCApplicationHandle.swift in Sources */, F3754A7D2CF87D600009312E /* SetupPasscodeView.swift in Sources */, F73EF7D72B0226080087E6E9 /* NCManageDatabase+Tip.swift in Sources */, @@ -4778,6 +4793,7 @@ AA8D31532D41052300FE2775 /* NCManageDatabase+DownloadLimit.swift in Sources */, F7A8D74228F18261008BBE1C /* NCUtility.swift in Sources */, F7A8D73A28F17E28008BBE1C /* NCManageDatabase+Video.swift in Sources */, + F7CAFE1F2F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */, F7D61EA72EBF1694007F865B /* NCManageDatabase+TableCapabilities.swift in Sources */, F7A8D73828F17E21008BBE1C /* NCManageDatabase+DashboardWidget.swift in Sources */, F7CF06852E1127460063AD04 /* NCManageDatabase+CreateMetadata.swift in Sources */, diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 74cfdf2dcd..d0f0c0643e 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -620,7 +620,7 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, fileName: fileName) Task { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadDataSource(serverUrl: serverUrl, requestData: true, status: nil) + delegate.transferReloadDataSource(serverUrl: self.serverUrl, requestData: true, status: nil) } } } else { diff --git a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift index fc10b27b2a..81f7c5ae75 100644 --- a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift +++ b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift @@ -200,7 +200,7 @@ class NCNetworkingE2EEUpload: NSObject { utility.createImageFileFrom(metadata: metadata) await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferChange(status: global.networkingStatusUploaded, + delegate.transferChange(status: self.global.networkingStatusUploaded, account: metadata.account, fileName: metadata.fileName, serverUrl: metadata.serverUrl, diff --git a/iOSClient/Networking/NCNetworking+Helper.swift b/iOSClient/Networking/NCNetworking+Helper.swift new file mode 100644 index 0000000000..00943c5375 --- /dev/null +++ b/iOSClient/Networking/NCNetworking+Helper.swift @@ -0,0 +1,220 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2019 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import UIKit +import NextcloudKit +import Alamofire + +protocol NCTransferDelegate: AnyObject { + var sceneIdentifier: String { get } + + func transferChange(status: String, + account: String, + fileName: String, + serverUrl: String, + selector: String?, + ocId: String, + destination: String?, + error: NKError) + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) + func transferReloadData(serverUrl: String?) + func transferProgressDidUpdate(progress: Float, + totalBytes: Int64, + totalBytesExpected: Int64, + fileName: String, + serverUrl: String) +} + +/// Actor-based dispatcher that manages weak NCTransferDelegate references +/// and delivers notifications safely across concurrency domains. +actor NCTransferDelegateDispatcher { + // Weak reference collection of delegates + private var transferDelegates = NSHashTable.weakObjects() + + /// Adds a delegate safely. + func addDelegate(_ delegate: NCTransferDelegate) { + transferDelegates.add(delegate) + } + + /// Remove a delegate safely. + func removeDelegate(_ delegate: NCTransferDelegate) { + transferDelegates.remove(delegate) + } + + /// Returns a strong snapshot of all valid delegates. + private func snapshotDelegates() -> [NCTransferDelegate] { + transferDelegates.allObjects.compactMap { $0 as? NCTransferDelegate } + } + + /// Notifies all delegates on the main actor. + func notifyAllDelegates(_ block: @MainActor @escaping (NCTransferDelegate) -> Void) async { + let delegates = snapshotDelegates() + await MainActor.run { + for delegate in delegates { + block(delegate) + } + } + } + + /// Notifies only the delegate matching a specific scene identifier. + func notifyDelegate(forScene sceneIdentifier: String,_ block: @MainActor @escaping (NCTransferDelegate) -> Void) async { + let delegates = snapshotDelegates() + await MainActor.run { + for delegate in delegates where delegate.sceneIdentifier == sceneIdentifier { + block(delegate) + } + } + } + + /// Notifies matching and non-matching delegates on the main actor. + func notifyDelegates(forScene sceneIdentifier: String, matching: @MainActor @escaping (NCTransferDelegate) -> Void, others: @MainActor @escaping (NCTransferDelegate) -> Void) async { + let delegates = snapshotDelegates() + await MainActor.run { + for delegate in delegates { + if delegate.sceneIdentifier == sceneIdentifier { + matching(delegate) + } else { + others(delegate) + } + } + } + } + + /// Notifies all delegates concurrently using async/await. + func notifyAllDelegatesAsync(_ block: @escaping @Sendable (NCTransferDelegate) async -> Void) async { + let delegates = snapshotDelegates() + await withTaskGroup(of: Void.self) { group in + for delegate in delegates { + group.addTask { + await block(delegate) + } + } + } + } +} + +/// A thread-safe registry for tracking in-flight `URLSessionTask` instances. +/// +/// Each task is associated with a string identifier (`identifier`) that you define, +/// allowing you to check whether a request is already running, avoid duplicates, +/// and cancel all active tasks at once. The registry automatically removes +/// completed tasks via `cleanupCompleted()` to keep memory usage compact. +/// +/// Typical use cases: +/// - Ensure only one task per identifier is active at a time. +/// - Query whether a specific request is still running (`isReading`). +/// - Forcefully stop a specific request (`cancel`). +/// - Forcefully stop all tasks when leaving a screen (`cancelAll`). +actor NetworkingTasks { + private var active: [(identifier: String, task: URLSessionTask)] = [] + + /// Returns whether there is an in-flight task for the given URL. + /// + /// A task is considered in-flight if its `state` is `.running` or `.suspended`. + /// - Parameter identifier: The identifier to check. + /// - Returns: `true` if a matching in-flight task exists; otherwise `false`. + func isReading(identifier: String) -> Bool { + // Drop finished/canceling tasks globally + cleanup() + + return active.contains { + $0.identifier == identifier && ($0.task.state == .running || $0.task.state == .suspended) + } + } + + /// Tracks a newly created `URLSessionTask` for the given identifier. + /// + /// If a running entry for the same identifier exists, it is removed before appending the new one. + /// - Parameters: + /// - identifier: The identifier associated with the task. + /// - task: The `URLSessionTask` to track. + func track(identifier: String, task: URLSessionTask) { + // Drop finished/canceling tasks globally + cleanup() + + active.removeAll { + $0.identifier == identifier && $0.task.state == .running + } + active.append((identifier, task)) + nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .start, message: "Start task for identifier: \(identifier)", consoleOnly: true) + } + + /// create a Identifier + /// + func createIdentifier(account: String? = nil, path: String? = nil, name: String) -> String { + if let account, + let path { + return account + "_" + path + "_" + name + } else if let path { + return path + "_" + name + } else { + return name + } + } + + /// Cancels and removes all tasks associated with the given id. + /// + /// - Parameter identifier: The identifier whose tasks should be canceled. + func cancel(identifier: String) { + // Drop finished/canceling tasks globally + cleanup() + + for element in active where element.identifier == identifier { + element.task.cancel() + nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .cancel, message: "Cancel task for identifier: \(identifier)", consoleOnly: true) + } + active.removeAll { + $0.identifier == identifier + } + } + + /// Cancels all tracked `URLSessionTask` and clears the registry. + /// + /// Call this when leaving the page/screen or when the operation must be forcefully stopped. + func cancelAll() { + active.forEach { + $0.task.cancel() + nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .cancel, message: "Cancel task with identifier: \($0.identifier)", consoleOnly: true) + } + active.removeAll() + } + + /// Removes tasks that have completed from the registry. + /// + /// Useful to keep the in-memory list compact during long-running operations. + func cleanup() { + active.removeAll { + $0.task.state == .completed || $0.task.state == .canceling + } + } +} + +/// Quantizes per-task progress updates to integer percentages (0...100). +/// Each (serverUrlFileName) pair is tracked separately, so you get +/// at most one update per integer percent for each transfer. +actor ProgressQuantizer { + private var lastPercent: [String: Int] = [:] + + /// Returns `true` only when integer percent changes (or hits 100). + /// + /// - Parameters: + /// - serverUrlFileName: The name of the file being transferred. + /// - fraction: Progress fraction [0.0 ... 1.0]. + func shouldEmit(serverUrlFileName: String, fraction: Double) -> Bool { + let percent = min(max(Int((fraction * 100).rounded(.down)), 0), 100) + + let last = lastPercent[serverUrlFileName] ?? -1 + guard percent != last || percent == 100 else { + return false + } + + lastPercent[serverUrlFileName] = percent + return true + } + + /// Clears stored state for a finished transfer. + func clear(serverUrlFileName: String) { + lastPercent.removeValue(forKey: serverUrlFileName) + } +} diff --git a/iOSClient/Networking/NCNetworking.swift b/iOSClient/Networking/NCNetworking.swift index 265363922b..e5c2611c06 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -17,206 +17,6 @@ import Alamofire func didAskForClientCertificate() } -protocol NCTransferDelegate: AnyObject { - var sceneIdentifier: String { get } - - func transferChange(status: String, - account: String, - fileName: String, - serverUrl: String, - selector: String?, - ocId: String, - destination: String?, - error: NKError) - func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) - func transferReloadData(serverUrl: String?) - func transferProgressDidUpdate(progress: Float, - totalBytes: Int64, - totalBytesExpected: Int64, - fileName: String, - serverUrl: String) -} - -/// Actor-based delegate dispatcher using weak references. -actor NCTransferDelegateDispatcher { - // Weak reference collection of delegates - private var transferDelegates = NSHashTable.weakObjects() - - /// Adds a delegate safely. - func addDelegate(_ delegate: NCTransferDelegate) { - transferDelegates.add(delegate) - } - - /// Remove a delegate safely. - func removeDelegate(_ delegate: NCTransferDelegate) { - transferDelegates.remove(delegate) - } - - /// Notifies all delegates. - func notifyAllDelegates(_ block: (NCTransferDelegate) -> Void) { - let delegatesCopy = transferDelegates.allObjects.compactMap { $0 as? NCTransferDelegate } - for delegate in delegatesCopy { - block(delegate) - } - } - - func notifyAllDelegatesAsync(_ block: @escaping (NCTransferDelegate) async -> Void) async { - let delegatesCopy = transferDelegates.allObjects.compactMap { $0 as? NCTransferDelegate } - for delegate in delegatesCopy { - await block(delegate) - } - } - - /// Notifies the delegate for a specific scene. - func notifyDelegate(forScene sceneIdentifier: String, _ block: (NCTransferDelegate) -> Void) { - let delegatesCopy = transferDelegates.allObjects.compactMap { $0 as? NCTransferDelegate } - for delegate in delegatesCopy { - if delegate.sceneIdentifier == sceneIdentifier { - block(delegate) - } - } - } - - /// Notifies matching and non-matching delegates for a specific scene. - func notifyDelegates(forScene sceneIdentifier: String, - matching: (NCTransferDelegate) -> Void, - others: (NCTransferDelegate) -> Void) { - let delegatesCopy = transferDelegates.allObjects.compactMap { $0 as? NCTransferDelegate } - for delegate in delegatesCopy { - if delegate.sceneIdentifier == sceneIdentifier { - matching(delegate) - } else { - others(delegate) - } - } - } -} - -/// A thread-safe registry for tracking in-flight `URLSessionTask` instances. -/// -/// Each task is associated with a string identifier (`identifier`) that you define, -/// allowing you to check whether a request is already running, avoid duplicates, -/// and cancel all active tasks at once. The registry automatically removes -/// completed tasks via `cleanupCompleted()` to keep memory usage compact. -/// -/// Typical use cases: -/// - Ensure only one task per identifier is active at a time. -/// - Query whether a specific request is still running (`isReading`). -/// - Forcefully stop a specific request (`cancel`). -/// - Forcefully stop all tasks when leaving a screen (`cancelAll`). -actor NetworkingTasks { - private var active: [(identifier: String, task: URLSessionTask)] = [] - - /// Returns whether there is an in-flight task for the given URL. - /// - /// A task is considered in-flight if its `state` is `.running` or `.suspended`. - /// - Parameter identifier: The identifier to check. - /// - Returns: `true` if a matching in-flight task exists; otherwise `false`. - func isReading(identifier: String) -> Bool { - // Drop finished/canceling tasks globally - cleanup() - - return active.contains { - $0.identifier == identifier && ($0.task.state == .running || $0.task.state == .suspended) - } - } - - /// Tracks a newly created `URLSessionTask` for the given identifier. - /// - /// If a running entry for the same identifier exists, it is removed before appending the new one. - /// - Parameters: - /// - identifier: The identifier associated with the task. - /// - task: The `URLSessionTask` to track. - func track(identifier: String, task: URLSessionTask) { - // Drop finished/canceling tasks globally - cleanup() - - active.removeAll { - $0.identifier == identifier && $0.task.state == .running - } - active.append((identifier, task)) - nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .start, message: "Start task for identifier: \(identifier)", consoleOnly: true) - } - - /// create a Identifier - /// - func createIdentifier(account: String? = nil, path: String? = nil, name: String) -> String { - if let account, - let path { - return account + "_" + path + "_" + name - } else if let path { - return path + "_" + name - } else { - return name - } - } - - /// Cancels and removes all tasks associated with the given id. - /// - /// - Parameter identifier: The identifier whose tasks should be canceled. - func cancel(identifier: String) { - // Drop finished/canceling tasks globally - cleanup() - - for element in active where element.identifier == identifier { - element.task.cancel() - nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .cancel, message: "Cancel task for identifier: \(identifier)", consoleOnly: true) - } - active.removeAll { - $0.identifier == identifier - } - } - - /// Cancels all tracked `URLSessionTask` and clears the registry. - /// - /// Call this when leaving the page/screen or when the operation must be forcefully stopped. - func cancelAll() { - active.forEach { - $0.task.cancel() - nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .cancel, message: "Cancel task with identifier: \($0.identifier)", consoleOnly: true) - } - active.removeAll() - } - - /// Removes tasks that have completed from the registry. - /// - /// Useful to keep the in-memory list compact during long-running operations. - func cleanup() { - active.removeAll { - $0.task.state == .completed || $0.task.state == .canceling - } - } -} - -/// Quantizes per-task progress updates to integer percentages (0...100). -/// Each (serverUrlFileName) pair is tracked separately, so you get -/// at most one update per integer percent for each transfer. -actor ProgressQuantizer { - private var lastPercent: [String: Int] = [:] - - /// Returns `true` only when integer percent changes (or hits 100). - /// - /// - Parameters: - /// - serverUrlFileName: The name of the file being transferred. - /// - fraction: Progress fraction [0.0 ... 1.0]. - func shouldEmit(serverUrlFileName: String, fraction: Double) -> Bool { - let percent = min(max(Int((fraction * 100).rounded(.down)), 0), 100) - - let last = lastPercent[serverUrlFileName] ?? -1 - guard percent != last || percent == 100 else { - return false - } - - lastPercent[serverUrlFileName] = percent - return true - } - - /// Clears stored state for a finished transfer. - func clear(serverUrlFileName: String) { - lastPercent.removeValue(forKey: serverUrlFileName) - } -} - class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { static let shared = NCNetworking() diff --git a/iOSClient/Share/NCSharePaging.swift b/iOSClient/Share/NCSharePaging.swift index 87dc37e449..c5177afdb8 100644 --- a/iOSClient/Share/NCSharePaging.swift +++ b/iOSClient/Share/NCSharePaging.swift @@ -126,7 +126,7 @@ class NCSharePaging: UIViewController { super.viewWillDisappear(animated) Task { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: nil) + delegate.transferReloadDataSource(serverUrl: self.metadata.serverUrl, requestData: false, status: nil) } } } diff --git a/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift b/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift index 1961ed341a..40215c7214 100644 --- a/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift +++ b/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift @@ -361,7 +361,7 @@ class NCViewerRichDocument: UIViewController, WKNavigationDelegate, WKScriptMess NCActivityIndicator.shared.stop() } - // MARK: - Hekper + // MARK: - Helper func filenameFromContentDisposition(_ disposition: String) -> String? { if let range = disposition.range(of: "filename=") { @@ -385,7 +385,7 @@ extension NCViewerRichDocument: UINavigationControllerDelegate { Task { if parent == nil { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: nil) + delegate.transferReloadDataSource(serverUrl: self.metadata.serverUrl, requestData: false, status: nil) } } } From 3dd3fe480394313085347d2d992990f6ad7e7fb3 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 14 Jan 2026 11:39:55 +0100 Subject: [PATCH 49/63] Improved code Signed-off-by: Marino Faggiana --- .../GUI/Lucid Banner/HudBannerView.swift | 1 - .../Collection Common/Cell/NCListCell.swift | 1 - .../NCCollectionViewCommon.swift | 11 ++++--- .../NCCollectionViewUnifiedSearch.swift | 2 +- .../NCMedia+CollectionViewDataSource.swift | 3 +- iOSClient/Menu/NCContextMenu.swift | 2 +- .../Networking/NCNetworking+Helper.swift | 2 +- .../NCNetworking+Recommendations.swift | 2 +- .../Networking/NCNetworking+WebDAV.swift | 31 +++++++++++++------ .../Settings/Settings/NCSettingsView.swift | 1 - .../StatusMessage/NCStatusMessageView.swift | 2 +- iOSClient/Trash/NCTrash+Networking.swift | 2 +- 12 files changed, 35 insertions(+), 25 deletions(-) diff --git a/iOSClient/GUI/Lucid Banner/HudBannerView.swift b/iOSClient/GUI/Lucid Banner/HudBannerView.swift index 34b9236cec..6d1802c1f0 100644 --- a/iOSClient/GUI/Lucid Banner/HudBannerView.swift +++ b/iOSClient/GUI/Lucid Banner/HudBannerView.swift @@ -208,7 +208,6 @@ struct HudBannerView: View { @ViewBuilder func containerView(@ViewBuilder _ content: () -> Content) -> some View { let cornerRadius: CGFloat = 22 - let opacity = 0.65 let backgroundColor = Color(.systemBackground).opacity(0.65) if #available(iOS 26, *) { diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index a5a501e4f4..5aa3f68e38 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -414,4 +414,3 @@ class BidiFilenameLabel: UILabel { return result } } - diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index d0f0c0643e..c8e6ac7303 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -237,9 +237,10 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, } NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterChangeTheming), object: nil, queue: .main) { _ in + let serverUrl = self.serverUrl Task { - await NCNetworking.shared.transferDispatcher.notifyAllDelegatesAsync { delegate in - await delegate.transferReloadData(serverUrl: self.serverUrl) + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferReloadData(serverUrl: serverUrl) } } } @@ -648,7 +649,7 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, } } - await NCNetworking.shared.transferDispatcher.notifyAllDelegatesAsync { delegate in + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in delegate.transferReloadData(serverUrl: self.serverUrl) } @@ -735,7 +736,7 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, metadataForSection.unifiedSearchInProgress = true Task { - await NCNetworking.shared.transferDispatcher.notifyAllDelegatesAsync { delegate in + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in delegate.transferReloadData(serverUrl: nil) } } @@ -761,7 +762,7 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, self.dataSource.appendMetadatasToSection(metadatas, metadataForSection: metadataForSection, lastSearchResult: searchResult) Task { - await NCNetworking.shared.transferDispatcher.notifyAllDelegatesAsync { delegate in + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in delegate.transferReloadData(serverUrl: nil) } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewUnifiedSearch.swift b/iOSClient/Main/Collection Common/NCCollectionViewUnifiedSearch.swift index 070f3d5ea4..3a926c9c9b 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewUnifiedSearch.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewUnifiedSearch.swift @@ -27,7 +27,7 @@ class NCCollectionViewUnifiedSearch: ConcurrentOperation, @unchecked Sendable { self.finish() Task { - await NCNetworking.shared.transferDispatcher.notifyAllDelegatesAsync { delegate in + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in delegate.transferReloadData(serverUrl: nil) } } diff --git a/iOSClient/Media/NCMedia+CollectionViewDataSource.swift b/iOSClient/Media/NCMedia+CollectionViewDataSource.swift index e9ed267832..eee99e776e 100644 --- a/iOSClient/Media/NCMedia+CollectionViewDataSource.swift +++ b/iOSClient/Media/NCMedia+CollectionViewDataSource.swift @@ -84,8 +84,9 @@ extension NCMedia: UICollectionViewDataSource { if isPinchGestureActive || ext == global.previewExt512 || ext == global.previewExt1024 { cell.imageItem.image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext, userId: self.session.userId, urlBase: self.session.urlBase) } else { + let session = self.session DispatchQueue.global(qos: .userInteractive).async { - let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext, userId: self.session.userId, urlBase: self.session.urlBase) + let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext, userId: session.userId, urlBase: session.urlBase) DispatchQueue.main.async { if let currentCell = collectionView.cellForItem(at: indexPath) as? NCMediaCell, currentCell.ocId == metadata.ocId, let image { diff --git a/iOSClient/Menu/NCContextMenu.swift b/iOSClient/Menu/NCContextMenu.swift index d8b36fbe99..8bdb17c7a9 100644 --- a/iOSClient/Menu/NCContextMenu.swift +++ b/iOSClient/Menu/NCContextMenu.swift @@ -516,7 +516,7 @@ class NCContextMenu: NSObject { } var iconImage: UIImage - + if let iconUrl = item.icon, let url = URL(string: metadata.urlBase + iconUrl) { let (data, _) = try await URLSession.shared.data(from: url) diff --git a/iOSClient/Networking/NCNetworking+Helper.swift b/iOSClient/Networking/NCNetworking+Helper.swift index 00943c5375..01aa965701 100644 --- a/iOSClient/Networking/NCNetworking+Helper.swift +++ b/iOSClient/Networking/NCNetworking+Helper.swift @@ -58,7 +58,7 @@ actor NCTransferDelegateDispatcher { } /// Notifies only the delegate matching a specific scene identifier. - func notifyDelegate(forScene sceneIdentifier: String,_ block: @MainActor @escaping (NCTransferDelegate) -> Void) async { + func notifyDelegate(forScene sceneIdentifier: String, _ block: @MainActor @escaping (NCTransferDelegate) -> Void) async { let delegates = snapshotDelegates() await MainActor.run { for delegate in delegates where delegate.sceneIdentifier == sceneIdentifier { diff --git a/iOSClient/Networking/NCNetworking+Recommendations.swift b/iOSClient/Networking/NCNetworking+Recommendations.swift index 22ba10e39c..a61c1dd7f0 100644 --- a/iOSClient/Networking/NCNetworking+Recommendations.swift +++ b/iOSClient/Networking/NCNetworking+Recommendations.swift @@ -51,7 +51,7 @@ extension NCNetworking { await NCManageDatabase.shared.createRecommendedFilesAsync(account: session.account, recommendations: recommendationsToInsert) - await NCNetworking.shared.transferDispatcher.notifyAllDelegatesAsync { delegate in + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in delegate.transferReloadData(serverUrl: serverUrl) } } diff --git a/iOSClient/Networking/NCNetworking+WebDAV.swift b/iOSClient/Networking/NCNetworking+WebDAV.swift index 6742ff5a75..9a016c7429 100644 --- a/iOSClient/Networking/NCNetworking+WebDAV.swift +++ b/iOSClient/Networking/NCNetworking+WebDAV.swift @@ -448,12 +448,14 @@ extension NCNetworking { serverUrls.insert(metadata.serverUrl) } + let ocIdss = ocIds + let serverUrlss = serverUrls await self.transferDispatcher.notifyAllDelegatesAsync { delegate in - for ocId in ocIds { + for ocId in ocIdss { await NCManageDatabase.shared.setMetadataSessionAsync(ocId: ocId, status: self.global.metadataStatusWaitDelete) } - serverUrls.forEach { serverUrl in + serverUrlss.forEach { serverUrl in delegate.transferReloadDataSource(serverUrl: serverUrl, requestData: false, status: self.global.metadataStatusWaitDelete) } } @@ -531,9 +533,11 @@ extension NCNetworking { #endif } else { Task { + let ocId = metadata.ocId + let serverUrl = metadata.serverUrl await self.transferDispatcher.notifyAllDelegatesAsync { delegate in - await NCManageDatabase.shared.renameMetadata(fileNameNew: fileNameNew, ocId: metadata.ocId, status: self.global.metadataStatusWaitRename) - delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: self.global.metadataStatusWaitRename) + await NCManageDatabase.shared.renameMetadata(fileNameNew: fileNameNew, ocId: ocId, status: self.global.metadataStatusWaitRename) + delegate.transferReloadDataSource(serverUrl: serverUrl, requestData: false, status: self.global.metadataStatusWaitRename) } } } @@ -583,9 +587,11 @@ extension NCNetworking { } Task { + let ocId = metadata.ocId + let serverUrl = metadata.serverUrl await self.transferDispatcher.notifyAllDelegatesAsync { delegate in - await NCManageDatabase.shared.setMetadataCopyMoveAsync(ocId: metadata.ocId, destination: destination, overwrite: overwrite.description, status: self.global.metadataStatusWaitMove) - delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: self.global.metadataStatusWaitMove) + await NCManageDatabase.shared.setMetadataCopyMoveAsync(ocId: ocId, destination: destination, overwrite: overwrite.description, status: self.global.metadataStatusWaitMove) + delegate.transferReloadDataSource(serverUrl: serverUrl, requestData: false, status: self.global.metadataStatusWaitMove) } } } @@ -645,9 +651,11 @@ extension NCNetworking { } Task { + let ocId = metadata.ocId + let serverUrl = metadata.serverUrl await self.transferDispatcher.notifyAllDelegatesAsync { delegate in - await NCManageDatabase.shared.setMetadataCopyMoveAsync(ocId: metadata.ocId, destination: destination, overwrite: overwrite.description, status: self.global.metadataStatusWaitCopy) - delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: self.global.metadataStatusWaitCopy) + await NCManageDatabase.shared.setMetadataCopyMoveAsync(ocId: ocId, destination: destination, overwrite: overwrite.description, status: self.global.metadataStatusWaitCopy) + delegate.transferReloadDataSource(serverUrl: serverUrl, requestData: false, status: self.global.metadataStatusWaitCopy) } } } @@ -705,9 +713,12 @@ extension NCNetworking { } Task { + let ocId = metadata.ocId + let serverUrl = metadata.serverUrl + let favorite = metadata.favorite await self.transferDispatcher.notifyAllDelegatesAsync { delegate in - await NCManageDatabase.shared.setMetadataFavoriteAsync(ocId: metadata.ocId, favorite: !metadata.favorite, saveOldFavorite: metadata.favorite.description, status: self.global.metadataStatusWaitFavorite) - delegate.transferReloadDataSource(serverUrl: metadata.serverUrl, requestData: false, status: self.global.metadataStatusWaitFavorite) + await NCManageDatabase.shared.setMetadataFavoriteAsync(ocId: ocId, favorite: !favorite, saveOldFavorite: favorite.description, status: self.global.metadataStatusWaitFavorite) + delegate.transferReloadDataSource(serverUrl: serverUrl, requestData: false, status: self.global.metadataStatusWaitFavorite) } } } diff --git a/iOSClient/Settings/Settings/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index dbbcd83492..63a614357a 100644 --- a/iOSClient/Settings/Settings/NCSettingsView.swift +++ b/iOSClient/Settings/Settings/NCSettingsView.swift @@ -290,4 +290,3 @@ struct E2EESection: View { #Preview { NCSettingsView(model: NCSettingsModel(controller: nil)) } - diff --git a/iOSClient/StatusMessage/NCStatusMessageView.swift b/iOSClient/StatusMessage/NCStatusMessageView.swift index 1c6c10c920..01b4060193 100644 --- a/iOSClient/StatusMessage/NCStatusMessageView.swift +++ b/iOSClient/StatusMessage/NCStatusMessageView.swift @@ -31,7 +31,7 @@ struct NCStatusMessageView: View { } } .padding(.top, 8) - + HStack { Text("_clear_status_message_after_") Menu { diff --git a/iOSClient/Trash/NCTrash+Networking.swift b/iOSClient/Trash/NCTrash+Networking.swift index 30e27c75bc..39aedb4fab 100644 --- a/iOSClient/Trash/NCTrash+Networking.swift +++ b/iOSClient/Trash/NCTrash+Networking.swift @@ -38,8 +38,8 @@ extension NCTrash { let resultsListingTrash = await NextcloudKit.shared.listingTrashAsync(filename: filename, showHiddenFiles: false, account: session.account) { task in Task { await NCNetworking.shared.networkingTasks.track(identifier: "NCTrash", task: task) + await self.collectionView.reloadData() } - self.collectionView.reloadData() } if let items = resultsListingTrash.items { From b3c1eaf0aaf65e91b93156da000e7f8dbd22a6b8 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 14 Jan 2026 17:05:11 +0100 Subject: [PATCH 50/63] fix Signed-off-by: Marino Faggiana --- .../NCCollectionViewCommon.swift | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index c8e6ac7303..da170aa7f1 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -152,7 +152,7 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, } internal let debouncerReloadDataSource = NCDebouncer(maxEventCount: NCBrandOptions.shared.numMaximumProcess) - internal let debouncerReloadData = NCDebouncer(delay: .seconds(0.5), maxEventCount: NCBrandOptions.shared.numMaximumProcess) + internal let debouncerReloadData = NCDebouncer(maxEventCount: NCBrandOptions.shared.numMaximumProcess) internal let debouncerGetServerData = NCDebouncer(maxEventCount: NCBrandOptions.shared.numMaximumProcess) internal let debouncerNetworkSearch = NCDebouncer(maxEventCount: NCBrandOptions.shared.numMaximumProcess) @@ -271,6 +271,8 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, isEditMode = false Task { + await NCNetworking.shared.transferDispatcher.addDelegate(self) + await (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() await (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() } @@ -296,10 +298,6 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - Task { - await NCNetworking.shared.transferDispatcher.addDelegate(self) - } - NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(closeRichWorkspaceWebView), name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) } @@ -906,13 +904,10 @@ extension NCCollectionViewCommon: NCTransferDelegate { func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } func transferReloadData(serverUrl: String?) { - guard serverUrl == self.serverUrl || serverUrl == nil else { - return - } Task { - await self.debouncerReloadData.call { + await self.debouncerReloadData.call({ self.collectionView.reloadData() - } + }, immediate: true) } } From 770a0befbffad7e7eb09e2a54dde47edc4caf643 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 15 Jan 2026 09:28:01 +0100 Subject: [PATCH 51/63] hide button more in photo cell Signed-off-by: Marino Faggiana --- iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift | 3 ++- .../NCCollectionViewCommon+CollectionViewDataSource.swift | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift index 8e04158586..0f6fac76ac 100644 --- a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift @@ -71,6 +71,7 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt imageVisualEffect.clipsToBounds = true imageVisualEffect.alpha = 0.5 + buttonMore.isHidden = true buttonMore.menu = nil buttonMore.showsMenuAsPrimaryAction = true contentView.bringSubviewToFront(buttonMore) @@ -93,7 +94,7 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt } func hideButtonMore(_ status: Bool) { - buttonMore.isHidden = status + // buttonMore.isHidden = status NO MORE USED } func hideImageStatus(_ status: Bool) { diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift index 7e437193cd..c5046327d8 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift @@ -59,7 +59,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.ocId = metadata.ocId cell.ocIdTransfer = metadata.ocIdTransfer - cell.hideButtonMore(true) + // cell.hideButtonMore(true) NO MORE USED cell.hideImageStatus(true) // Image @@ -105,7 +105,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } if width > 100 { - cell.hideButtonMore(false) + // cell.hideButtonMore(false) NO MORE USED cell.hideImageStatus(false) } From 670eb6c4a17a6c197d376ffe038350cbc5ae1cc9 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 15 Jan 2026 09:56:19 +0100 Subject: [PATCH 52/63] improved Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCNetworking+Helper.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iOSClient/Networking/NCNetworking+Helper.swift b/iOSClient/Networking/NCNetworking+Helper.swift index 01aa965701..71a0573055 100644 --- a/iOSClient/Networking/NCNetworking+Helper.swift +++ b/iOSClient/Networking/NCNetworking+Helper.swift @@ -34,6 +34,9 @@ actor NCTransferDelegateDispatcher { /// Adds a delegate safely. func addDelegate(_ delegate: NCTransferDelegate) { + if transferDelegates.contains(delegate) { + return + } transferDelegates.add(delegate) } From 4063199b34c8aed80cc193b5632794c5ab995062 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 15 Jan 2026 09:57:41 +0100 Subject: [PATCH 53/63] 2026 Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 1a1645500c..90693af197 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -5762,7 +5762,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 5; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -5782,7 +5782,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Nextcloud. All rights reserved."; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Nextcloud. All rights reserved."; IPHONEOS_DEPLOYMENT_TARGET = 17.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -5828,7 +5828,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -5845,7 +5845,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Nextcloud. All rights reserved."; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Nextcloud. All rights reserved."; IPHONEOS_DEPLOYMENT_TARGET = 17.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", From 1a83ec616645dca8464c624119c4f7f69f162af1 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 15 Jan 2026 09:57:48 +0100 Subject: [PATCH 54/63] 2026 Signed-off-by: Marino Faggiana --- Brand/NCBrand.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Brand/NCBrand.swift b/Brand/NCBrand.swift index 1cd9756eb9..c53e2e82a5 100755 --- a/Brand/NCBrand.swift +++ b/Brand/NCBrand.swift @@ -25,7 +25,7 @@ final class NCBrandOptions: @unchecked Sendable { var brand: String = "Nextcloud" var brandUserAgent: String = "" - var textCopyrightNextcloudiOS: String = "Nextcloud Matheria for iOS %@ © 2025" + var textCopyrightNextcloudiOS: String = "Nextcloud Matheria for iOS %@ © 2026" var textCopyrightNextcloudServer: String = "Nextcloud Server %@" var loginBaseUrl: String = "https://cloud.nextcloud.com" var pushNotificationServerProxy: String = "" From f966d585f180d32acdd06490bf0247edf589fbc1 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 15 Jan 2026 11:13:18 +0100 Subject: [PATCH 55/63] cell protocol improvements Signed-off-by: Marino Faggiana --- Share/NCShareExtension+DataSource.swift | 6 +- iOSClient/Activity/NCActivity.swift | 8 +- .../Activity/NCActivityTableViewCell.swift | 2 +- .../Cell/NCCellProtocol.swift | 61 +++--- .../Collection Common/Cell/NCGridCell.swift | 37 +--- .../Collection Common/Cell/NCListCell.swift | 54 ++--- .../Collection Common/Cell/NCPhotoCell.swift | 26 +-- .../Cell/NCRecommendationsCell.swift | 10 +- .../NCCollectionViewCommon+CellDelegate.swift | 10 +- ...nViewCommon+CollectionViewDataSource.swift | 192 ++++++++---------- .../NCCollectionViewDownloadThumbnail.swift | 24 ++- .../NCSectionFirstHeader.swift | 9 +- .../Networking/NCNetworking+WebDAV.swift | 12 +- iOSClient/Notification/NCNotification.swift | 6 +- iOSClient/Select/NCSelect.swift | 10 +- iOSClient/Share/NCShareCommentsCell.swift | 2 +- iOSClient/Share/NCShareUserCell.swift | 4 +- 17 files changed, 199 insertions(+), 274 deletions(-) diff --git a/Share/NCShareExtension+DataSource.swift b/Share/NCShareExtension+DataSource.swift index f6ac57ec99..5b1b3cbb83 100644 --- a/Share/NCShareExtension+DataSource.swift +++ b/Share/NCShareExtension+DataSource.swift @@ -76,14 +76,12 @@ extension NCShareExtension: UICollectionViewDataSource { } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell = (collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as? NCListCell)! + var cell = (collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as? NCListCell)! guard let metadata = self.dataSource.getMetadata(indexPath: indexPath) else { return cell } - cell.fileOcId = metadata.ocId - cell.fileOcIdTransfer = metadata.ocIdTransfer - cell.fileUser = metadata.ownerId + cell.metadata = metadata cell.labelTitle.text = metadata.fileNameView cell.labelTitle.textColor = NCBrandColor.shared.textColor cell.imageSelect.image = nil diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index 6098a49209..e1335fe29c 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -225,9 +225,9 @@ extension NCActivity: UITableViewDataSource { let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) if results.image == nil { - cell.fileAvatarImageView?.image = utility.loadUserImage(for: comment.actorId, displayName: comment.actorDisplayName, urlBase: NCSession.shared.getSession(account: account).urlBase) + cell.avatarImageView?.image = utility.loadUserImage(for: comment.actorId, displayName: comment.actorDisplayName, urlBase: NCSession.shared.getSession(account: account).urlBase) } else { - cell.fileAvatarImageView?.image = results.image + cell.avatarImageView?.image = results.image } if let tblAvatar = results.tblAvatar, @@ -313,9 +313,9 @@ extension NCActivity: UITableViewDataSource { let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) if results.image == nil { - cell.fileAvatarImageView?.image = utility.loadUserImage(for: activity.user, displayName: nil, urlBase: session.urlBase) + cell.avatarImageView?.image = utility.loadUserImage(for: activity.user, displayName: nil, urlBase: session.urlBase) } else { - cell.fileAvatarImageView?.image = results.image + cell.avatarImageView?.image = results.image } if !(results.tblAvatar?.loaded ?? false), diff --git a/iOSClient/Activity/NCActivityTableViewCell.swift b/iOSClient/Activity/NCActivityTableViewCell.swift index 2b0db6b02c..1a1f5510f0 100644 --- a/iOSClient/Activity/NCActivityTableViewCell.swift +++ b/iOSClient/Activity/NCActivityTableViewCell.swift @@ -37,7 +37,7 @@ class NCActivityTableViewCell: UITableViewCell, NCCellProtocol { get { return index } set { index = newValue } } - var fileAvatarImageView: UIImageView? { + var avatarImageView: UIImageView? { return avatar } var fileUser: String? { diff --git a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift index c9ee6c7db1..7cd62207e9 100644 --- a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift +++ b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift @@ -5,21 +5,17 @@ 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 avatarImageView: UIImageView? { get } + var metadata: tableMetadata? {get set } + var previewImageView: UIImageView? { get set } + var title: UILabel? { get set } + var info: UILabel? { get set } + var subInfo: UILabel? { get set } + var statusImageView: UIImageView? { get set } + var localImageView: UIImageView? { get set } + var favoriteImageView: UIImageView? { get set } + var shareImageView: UIImageView? { get set } + var separatorView: UIView? { get set } func titleInfoTrailingDefault() func titleInfoTrailingFull() @@ -41,58 +37,47 @@ protocol NCCellProtocol { } extension NCCellProtocol { - var fileAvatarImageView: UIImageView? { + var avatarImageView: UIImageView? { return nil } - var fileAccount: String? { + var metadata: tableMetadata? { get { return nil } set {} } - var fileOcId: String? { + var previewImageView: UIImageView? { get { return nil } set {} } - var fileOcIdTransfer: String? { + var title: UILabel? { get { return nil } set {} } - var filePreviewImageView: UIImageView? { - get { return nil } - set {} - } - var fileTitleLabel: UILabel? { - get { return nil } - set {} - } - var fileInfoLabel: UILabel? { + var info: UILabel? { get { return nil } set { } } - var fileSubinfoLabel: UILabel? { + var subInfo: UILabel? { get { return nil } set { } } - var fileStatusImage: UIImageView? { + var statusImageView: UIImageView? { get { return nil } set {} } - var fileLocalImage: UIImageView? { + var localImageView: UIImageView? { get { return nil } set {} } - var fileFavoriteImage: UIImageView? { + var favoriteImageView: UIImageView? { get { return nil } set {} } - var fileSharedImage: UIImageView? { + var shareImageView: UIImageView? { get { return nil } set {} } - var fileMoreImage: UIImageView? { - get { return nil } - set {} - } - var cellSeparatorView: UIView? { + + var separatorView: UIView? { get { return nil } set {} } diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift index abde4ee50a..2182b9e141 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift @@ -5,8 +5,8 @@ import UIKit protocol NCGridCellDelegate: AnyObject { - func onMenuIntent(with ocId: String) - func contextMenu(with ocId: String, button: UIButton, sender: Any) + func onMenuIntent(with metadata: tableMetadata?) + func contextMenu(with metadata: tableMetadata?, button: UIButton, sender: Any) } class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProtocol { @@ -22,50 +22,35 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var imageVisualEffect: UIVisualEffectView! @IBOutlet weak var iconsStackView: UIStackView! - var ocId = "" { didSet { delegate?.contextMenu(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } - var ocIdTransfer = "" - var account = "" - var user = "" + var metadata: tableMetadata? { didSet { delegate?.contextMenu(with: metadata, button: buttonMore, sender: self) /* preconfigure UIMenu with each metadata */ } } weak var delegate: NCGridCellDelegate? - var fileOcId: String? { - get { return ocId } - set { ocId = newValue ?? "" } - } - var fileOcIdTransfer: String? { - get { return ocIdTransfer } - set { ocIdTransfer = newValue ?? "" } - } - var filePreviewImageView: UIImageView? { + var previewImageView: UIImageView? { get { return imageItem } set { imageItem = newValue } } - var fileUser: String? { - get { return user } - set { user = newValue ?? "" } - } - var fileTitleLabel: UILabel? { + var title: UILabel? { get { return labelTitle } set { labelTitle = newValue } } - var fileInfoLabel: UILabel? { + var info: UILabel? { get { return labelInfo } set { labelInfo = newValue } } - var fileSubinfoLabel: UILabel? { + var subInfo: UILabel? { get { return labelSubinfo } set { labelSubinfo = newValue } } - var fileStatusImage: UIImageView? { + var statusImageView: UIImageView? { get { return imageStatus } set { imageStatus = newValue } } - var fileLocalImage: UIImageView? { + var localImageView: UIImageView? { get { return imageLocal } set { imageLocal = newValue } } - var fileFavoriteImage: UIImageView? { + var favoriteImageView: UIImageView? { get { return imageFavorite } set { imageFavorite = newValue } } @@ -125,7 +110,7 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto let location = g.location(in: contentView) if buttonMore.frame.contains(location) { - delegate?.onMenuIntent(with: ocId) + delegate?.onMenuIntent(with: metadata) } } diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 5aa3f68e38..fb814f3c94 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -5,9 +5,9 @@ import UIKit protocol NCListCellDelegate: AnyObject { - func onMenuIntent(with ocId: String) - func contextMenu(with ocId: String, button: UIButton, sender: Any) - func tapShareListItem(with ocId: String, button: UIButton, sender: Any) + func onMenuIntent(with metadata: tableMetadata?) + func contextMenu(with metadata: tableMetadata?, button: UIButton, sender: Any) + func tapShareListItem(with metadata: tableMetadata?, button: UIButton, sender: Any) } class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProtocol { @@ -32,64 +32,50 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var separatorHeightConstraint: NSLayoutConstraint! @IBOutlet weak var titleTrailingConstraint: NSLayoutConstraint! - var ocId = "" { didSet { delegate?.contextMenu(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } - var ocIdTransfer = "" - var user = "" + var metadata: tableMetadata? { + didSet { + delegate?.contextMenu(with: metadata, button: buttonMore, sender: self) /* preconfigure UIMenu with each metadata */ + } + } weak var delegate: NCListCellDelegate? - var fileAvatarImageView: UIImageView? { + var avatarImageView: UIImageView? { return imageShared } - var fileOcId: String? { - get { return ocId } - set { ocId = newValue ?? "" } - } - var fileOcIdTransfer: String? { - get { return ocIdTransfer } - set { ocIdTransfer = newValue ?? "" } - } - var filePreviewImageView: UIImageView? { + var previewImageView: UIImageView? { get { return imageItem } set { imageItem = newValue } } - var fileUser: String? { - get { return user } - set { user = newValue ?? "" } - } - var fileTitleLabel: UILabel? { + var title: UILabel? { get { return labelTitle } set { labelTitle = newValue } } - var fileInfoLabel: UILabel? { + var info: UILabel? { get { return labelInfo } set { labelInfo = newValue } } - var fileSubinfoLabel: UILabel? { + var subInfo: UILabel? { get { return labelSubinfo } set { labelSubinfo = newValue } } - var fileStatusImage: UIImageView? { + var statusImageView: UIImageView? { get { return imageStatus } set { imageStatus = newValue } } - var fileLocalImage: UIImageView? { + var localImageView: UIImageView? { get { return imageLocal } set { imageLocal = newValue } } - var fileFavoriteImage: UIImageView? { + var favoriteImageView: UIImageView? { get { return imageFavorite } set { imageFavorite = newValue } } - var fileSharedImage: UIImageView? { + var shareImageView: UIImageView? { get { return imageShared } set { imageShared = newValue } } - var fileMoreImage: UIImageView? { - get { return imageMore } - set { imageMore = newValue } - } - var cellSeparatorView: UIView? { + var separatorView: UIView? { get { return separator } set { separator = newValue } } @@ -155,14 +141,14 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } @IBAction func touchUpInsideShare(_ sender: Any) { - delegate?.tapShareListItem(with: ocId, button: buttonShared, sender: sender) + delegate?.tapShareListItem(with: metadata, button: buttonShared, sender: sender) } @objc private func handleTapObserver(_ g: UITapGestureRecognizer) { let location = g.location(in: contentView) if buttonMore.frame.contains(location) { - delegate?.onMenuIntent(with: ocId) + delegate?.onMenuIntent(with: metadata) } } diff --git a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift index 0f6fac76ac..617292b934 100644 --- a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift @@ -5,8 +5,8 @@ import UIKit protocol NCPhotoCellDelegate: AnyObject { - func onMenuIntent(with ocId: String) - func contextMenu(with ocId: String, button: UIButton, sender: Any) + func onMenuIntent(with metadata: tableMetadata?) + func contextMenu(with metadata: tableMetadata?, button: UIButton, sender: Any) } class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProtocol { @@ -16,29 +16,15 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt @IBOutlet weak var buttonMore: UIButton! @IBOutlet weak var imageVisualEffect: UIVisualEffectView! - var ocId = "" { didSet { delegate?.contextMenu(with: ocId, button: buttonMore, sender: self) /* preconfigure UIMenu with each ocId */ } } - var ocIdTransfer = "" - var user = "" + var metadata: tableMetadata? { didSet { delegate?.contextMenu(with: metadata, button: buttonMore, sender: self) /* preconfigure UIMenu with each metadata */ } } weak var delegate: NCPhotoCellDelegate? - var fileOcId: String? { - get { return ocId } - set { ocId = newValue ?? "" } - } - var fileOcIdTransfer: String? { - get { return ocIdTransfer } - set { ocIdTransfer = newValue ?? "" } - } - var filePreviewImageView: UIImageView? { + var previewImageView: UIImageView? { get { return imageItem } set { imageItem = newValue } } - var fileUser: String? { - get { return user } - set { user = newValue ?? "" } - } - var fileStatusImage: UIImageView? { + var statusImageView: UIImageView? { get { return imageStatus } set { imageStatus = newValue } } @@ -85,7 +71,7 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt let location = g.location(in: contentView) if buttonMore.frame.contains(location) { - delegate?.onMenuIntent(with: ocId) + delegate?.onMenuIntent(with: metadata) } } diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift index 12b5474056..32a484471a 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift @@ -5,8 +5,8 @@ import UIKit protocol NCRecommendationsCellDelegate: AnyObject { - func onMenuIntent(with ocId: String) - func contextMenu(with metadata: tableMetadata, button: UIButton, sender: Any) + func onMenuIntent(with metadata: tableMetadata?) + func contextMenu(with metadata: tableMetadata?, button: UIButton, sender: Any) } class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { @@ -16,9 +16,9 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { @IBOutlet weak var buttonMore: UIButton! var delegate: NCRecommendationsCellDelegate? - var metadata: tableMetadata = tableMetadata() var recommendedFiles: tableRecommendedFiles = tableRecommendedFiles() - var id: String = "" { didSet { delegate?.contextMenu(with: metadata, button: buttonMore, sender: self) /* preconfigure UIMenu with each id set */ } } + + var metadata: tableMetadata? { didSet { delegate?.contextMenu(with: metadata, button: buttonMore, sender: self) /* preconfigure UIMenu with each metadata */ } } override func awakeFromNib() { super.awakeFromNib() @@ -58,7 +58,7 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { let location = g.location(in: contentView) if buttonMore.frame.contains(location) { - delegate?.onMenuIntent(with: metadata.ocId) + delegate?.onMenuIntent(with: metadata) } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift index f3b1e1da70..4aabde59f7 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift @@ -1,20 +1,20 @@ extension NCCollectionViewCommon: NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate { - func contextMenu(with ocId: String, button: UIButton, sender: Any) { + func contextMenu(with metadata: tableMetadata?, button: UIButton, sender: Any) { Task { - guard let metadata = await self.database.getMetadataFromOcIdAsync(ocId) else { return } + guard let metadata else { return } button.menu = NCContextMenu(metadata: metadata, viewController: self, sceneIdentifier: self.sceneIdentifier, sender: sender).viewMenu() } } - func onMenuIntent(with ocId: String) { + func onMenuIntent(with metadata: tableMetadata?) { Task { await self.debouncerReloadData.pause() } } - func tapShareListItem(with ocId: String, button: UIButton, sender: Any) { + func tapShareListItem(with metadata: tableMetadata?, button: UIButton, sender: Any) { Task { - guard let metadata = await self.database.getMetadataFromOcIdAsync(ocId) else { return } + guard let metadata else { return } NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift index c5046327d8..6b799ff6f5 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift @@ -57,8 +57,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { private func photoCell(cell: NCPhotoCell, indexPath: IndexPath, metadata: tableMetadata, ext: String) -> NCPhotoCell { let width = UIScreen.main.bounds.width / CGFloat(self.numberOfColumns) - cell.ocId = metadata.ocId - cell.ocIdTransfer = metadata.ocIdTransfer + cell.metadata = metadata // cell.hideButtonMore(true) NO MORE USED cell.hideImageStatus(true) @@ -66,13 +65,13 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { // if let image = NCImageCache.shared.getImageCache(ocId: metadata.ocId, etag: metadata.etag, ext: ext) { - cell.filePreviewImageView?.image = image - cell.filePreviewImageView?.contentMode = .scaleAspectFill + cell.previewImageView?.image = image + cell.previewImageView?.contentMode = .scaleAspectFill } else { if isPinchGestureActive || ext == global.previewExt512 || ext == global.previewExt1024 { - cell.filePreviewImageView?.image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext, userId: metadata.userId, urlBase: metadata.urlBase) + cell.previewImageView?.image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext, userId: metadata.userId, urlBase: metadata.urlBase) } DispatchQueue.global(qos: .userInteractive).async { @@ -80,16 +79,16 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if let image { self.imageCache.addImageCache(ocId: metadata.ocId, etag: metadata.etag, image: image, ext: ext, cost: indexPath.row) DispatchQueue.main.async { - cell.filePreviewImageView?.image = image - cell.filePreviewImageView?.contentMode = .scaleAspectFill + cell.previewImageView?.image = image + cell.previewImageView?.contentMode = .scaleAspectFill } } else { DispatchQueue.main.async { - cell.filePreviewImageView?.contentMode = .scaleAspectFit + cell.previewImageView?.contentMode = .scaleAspectFit if metadata.iconName.isEmpty { - cell.filePreviewImageView?.image = NCImageCache.shared.getImageFile() + cell.previewImageView?.image = NCImageCache.shared.getImageFile() } else { - cell.filePreviewImageView?.image = self.utility.loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) + cell.previewImageView?.image = self.utility.loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) } } } @@ -160,13 +159,13 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } // CONTENT MODE - cell.fileAvatarImageView?.contentMode = .center - cell.filePreviewImageView?.layer.borderWidth = 0 + cell.avatarImageView?.contentMode = .center + cell.previewImageView?.layer.borderWidth = 0 if existsImagePreview && layoutForView?.layout != global.layoutPhotoRatio { - cell.filePreviewImageView?.contentMode = .scaleAspectFill + cell.previewImageView?.contentMode = .scaleAspectFill } else { - cell.filePreviewImageView?.contentMode = .scaleAspectFit + cell.previewImageView?.contentMode = .scaleAspectFit } guard let metadata = self.dataSource.getMetadata(indexPath: indexPath) else { @@ -178,35 +177,25 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { isMounted = metadata.permissions.contains(NCMetadataPermissions.permissionMounted) && !metadataFolder!.permissions.contains(NCMetadataPermissions.permissionMounted) } - cell.fileAccount = metadata.account - cell.fileOcId = metadata.ocId - cell.fileOcIdTransfer = metadata.ocIdTransfer - cell.fileUser = metadata.ownerId + cell.metadata = metadata if isSearchingMode { if metadata.name == global.appName { - cell.fileInfoLabel?.text = NSLocalizedString("_in_", comment: "") + " " + utilityFileSystem.getPath(path: metadata.path, user: metadata.user) + cell.info?.text = NSLocalizedString("_in_", comment: "") + " " + utilityFileSystem.getPath(path: metadata.path, user: metadata.user) } else { - cell.fileInfoLabel?.text = metadata.subline + cell.info?.text = metadata.subline } - cell.fileSubinfoLabel?.isHidden = true + cell.subInfo?.isHidden = true } else if !metadata.sessionError.isEmpty, metadata.status != global.metadataStatusNormal { - cell.fileSubinfoLabel?.isHidden = false - cell.fileInfoLabel?.text = metadata.sessionError + cell.subInfo?.isHidden = false + cell.info?.text = metadata.sessionError } else { - cell.fileSubinfoLabel?.isHidden = false + cell.subInfo?.isHidden = false cell.writeInfoDateSize(date: metadata.date, size: metadata.size) } -// if cell is NCListCell && cell.fileTitleLabel is BidiFilenameLabel { -// (cell.fileTitleLabel as? BidiFilenameLabel)?.fullFilename = metadata.fileNameView -// (cell.fileTitleLabel as? BidiFilenameLabel)?.isFolder = metadata.directory -// (cell.fileTitleLabel as? BidiFilenameLabel)?.numberOfLines = 1 -// -// } else { - cell.fileTitleLabel?.text = metadata.fileNameView -// } + cell.title?.text = metadata.fileNameView // Accessibility [shared] if metadata.ownerId != appDelegate.userId, appDelegate.account == metadata.account { if metadata.ownerId != metadata.userId { @@ -217,89 +206,89 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { let tblDirectory = database.getTableDirectory(ocId: metadata.ocId) if metadata.e2eEncrypted { - cell.filePreviewImageView?.image = imageCache.getFolderEncrypted(account: metadata.account) + cell.previewImageView?.image = imageCache.getFolderEncrypted(account: metadata.account) } else if isShare { - cell.filePreviewImageView?.image = imageCache.getFolderSharedWithMe(account: metadata.account) + cell.previewImageView?.image = imageCache.getFolderSharedWithMe(account: metadata.account) } else if !metadata.shareType.isEmpty { metadata.shareType.contains(NKShare.ShareType.publicLink.rawValue) ? - (cell.filePreviewImageView?.image = imageCache.getFolderPublic(account: metadata.account)) : - (cell.filePreviewImageView?.image = imageCache.getFolderSharedWithMe(account: metadata.account)) + (cell.previewImageView?.image = imageCache.getFolderPublic(account: metadata.account)) : + (cell.previewImageView?.image = imageCache.getFolderSharedWithMe(account: metadata.account)) } else if !metadata.shareType.isEmpty && metadata.shareType.contains(NKShare.ShareType.publicLink.rawValue) { - cell.filePreviewImageView?.image = imageCache.getFolderPublic(account: metadata.account) + cell.previewImageView?.image = imageCache.getFolderPublic(account: metadata.account) } else if metadata.mountType == "group" { - cell.filePreviewImageView?.image = imageCache.getFolderGroup(account: metadata.account) + cell.previewImageView?.image = imageCache.getFolderGroup(account: metadata.account) } else if isMounted { - cell.filePreviewImageView?.image = imageCache.getFolderExternal(account: metadata.account) + cell.previewImageView?.image = imageCache.getFolderExternal(account: metadata.account) } else if metadata.fileName == autoUploadFileName && metadata.serverUrl == autoUploadDirectory { - cell.filePreviewImageView?.image = imageCache.getFolderAutomaticUpload(account: metadata.account) + cell.previewImageView?.image = imageCache.getFolderAutomaticUpload(account: metadata.account) } else { - cell.filePreviewImageView?.image = imageCache.getFolder(account: metadata.account) + cell.previewImageView?.image = imageCache.getFolder(account: metadata.account) } // Local image: offline if let tblDirectory, tblDirectory.offline { - cell.fileLocalImage?.image = imageCache.getImageOfflineFlag(colors: [.systemBackground, .systemGreen]) + cell.localImageView?.image = imageCache.getImageOfflineFlag(colors: [.systemBackground, .systemGreen]) } // color folder - cell.filePreviewImageView?.image = cell.filePreviewImageView?.image?.colorizeFolder(metadata: metadata, tblDirectory: tblDirectory) + cell.previewImageView?.image = cell.previewImageView?.image?.colorizeFolder(metadata: metadata, tblDirectory: tblDirectory) } else { let tableLocalFile = database.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) if metadata.hasPreviewBorder { - cell.filePreviewImageView?.layer.borderWidth = 0.2 - cell.filePreviewImageView?.layer.borderColor = UIColor.lightGray.cgColor + cell.previewImageView?.layer.borderWidth = 0.2 + cell.previewImageView?.layer.borderColor = UIColor.lightGray.cgColor } if metadata.name == global.appName { if let image = NCImageCache.shared.getImageCache(ocId: metadata.ocId, etag: metadata.etag, ext: ext) { - cell.filePreviewImageView?.image = image + cell.previewImageView?.image = image } else if let image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext, userId: metadata.userId, urlBase: metadata.urlBase) { - cell.filePreviewImageView?.image = image + cell.previewImageView?.image = image } - if cell.filePreviewImageView?.image == nil { + if cell.previewImageView?.image == nil { if metadata.iconName.isEmpty { - cell.filePreviewImageView?.image = NCImageCache.shared.getImageFile() + cell.previewImageView?.image = NCImageCache.shared.getImageFile() } else { - cell.filePreviewImageView?.image = utility.loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) + cell.previewImageView?.image = utility.loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) } } } else { // APP NAME - UNIFIED SEARCH switch metadata.iconName { case let str where str.contains("contacts"): - cell.filePreviewImageView?.image = utility.loadImage(named: "person.crop.rectangle.stack", colors: [NCBrandColor.shared.iconImageColor]) + cell.previewImageView?.image = utility.loadImage(named: "person.crop.rectangle.stack", colors: [NCBrandColor.shared.iconImageColor]) case let str where str.contains("conversation"): - cell.filePreviewImageView?.image = UIImage(named: "talk-template")!.image(color: NCBrandColor.shared.getElement(account: metadata.account)) + cell.previewImageView?.image = UIImage(named: "talk-template")!.image(color: NCBrandColor.shared.getElement(account: metadata.account)) case let str where str.contains("calendar"): - cell.filePreviewImageView?.image = utility.loadImage(named: "calendar", colors: [NCBrandColor.shared.iconImageColor]) + cell.previewImageView?.image = utility.loadImage(named: "calendar", colors: [NCBrandColor.shared.iconImageColor]) case let str where str.contains("deck"): - cell.filePreviewImageView?.image = utility.loadImage(named: "square.stack.fill", colors: [NCBrandColor.shared.iconImageColor]) + cell.previewImageView?.image = utility.loadImage(named: "square.stack.fill", colors: [NCBrandColor.shared.iconImageColor]) case let str where str.contains("mail"): - cell.filePreviewImageView?.image = utility.loadImage(named: "mail", colors: [NCBrandColor.shared.iconImageColor]) + cell.previewImageView?.image = utility.loadImage(named: "mail", colors: [NCBrandColor.shared.iconImageColor]) case let str where str.contains("talk"): - cell.filePreviewImageView?.image = UIImage(named: "talk-template")!.image(color: NCBrandColor.shared.getElement(account: metadata.account)) + cell.previewImageView?.image = UIImage(named: "talk-template")!.image(color: NCBrandColor.shared.getElement(account: metadata.account)) case let str where str.contains("confirm"): - cell.filePreviewImageView?.image = utility.loadImage(named: "arrow.right", colors: [NCBrandColor.shared.iconImageColor]) + cell.previewImageView?.image = utility.loadImage(named: "arrow.right", colors: [NCBrandColor.shared.iconImageColor]) case let str where str.contains("pages"): - cell.filePreviewImageView?.image = utility.loadImage(named: "doc.richtext", colors: [NCBrandColor.shared.iconImageColor]) + cell.previewImageView?.image = utility.loadImage(named: "doc.richtext", colors: [NCBrandColor.shared.iconImageColor]) default: - cell.filePreviewImageView?.image = utility.loadImage(named: "doc", colors: [NCBrandColor.shared.iconImageColor]) + cell.previewImageView?.image = utility.loadImage(named: "doc", colors: [NCBrandColor.shared.iconImageColor]) } if !metadata.iconUrl.isEmpty { if let ownerId = getAvatarFromIconUrl(metadata: metadata) { let fileName = NCSession.shared.getFileName(urlBase: metadata.urlBase, user: ownerId) if let image = NCImageCache.shared.getImageCache(key: fileName) { - cell.filePreviewImageView?.image = image + cell.previewImageView?.image = image } else { self.database.getImageAvatarLoaded(fileName: fileName) { image, tblAvatar in if let image { - cell.filePreviewImageView?.image = image + cell.previewImageView?.image = image NCImageCache.shared.addImageCache(image: image, key: fileName) } else { - cell.filePreviewImageView?.image = self.utility.loadUserImage(for: ownerId, displayName: nil, urlBase: metadata.urlBase) + cell.previewImageView?.image = self.utility.loadUserImage(for: ownerId, displayName: nil, urlBase: metadata.urlBase) } if !(tblAvatar?.loaded ?? false), @@ -314,27 +303,27 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if let tableLocalFile, tableLocalFile.offline { a11yValues.append(NSLocalizedString("_offline_", comment: "")) - cell.fileLocalImage?.image = imageCache.getImageOfflineFlag(colors: [.systemBackground, .systemGreen]) + cell.localImageView?.image = imageCache.getImageOfflineFlag(colors: [.systemBackground, .systemGreen]) } else if utilityFileSystem.fileProviderStorageExists(metadata) { - cell.fileLocalImage?.image = imageCache.getImageLocal(colors: [.systemBackground, .systemGreen]) + cell.localImageView?.image = imageCache.getImageLocal(colors: [.systemBackground, .systemGreen]) } } // image Favorite if metadata.favorite { - cell.fileFavoriteImage?.image = imageCache.getImageFavorite() + cell.favoriteImageView?.image = imageCache.getImageFavorite() a11yValues.append(NSLocalizedString("_favorite_short_", comment: "")) } // Share image if isShare { - cell.fileSharedImage?.image = imageCache.getImageShared() + cell.shareImageView?.image = imageCache.getImageShared() } else if !metadata.shareType.isEmpty { metadata.shareType.contains(NKShare.ShareType.publicLink.rawValue) ? - (cell.fileSharedImage?.image = imageCache.getImageShareByLink()) : - (cell.fileSharedImage?.image = imageCache.getImageShared()) + (cell.shareImageView?.image = imageCache.getImageShareByLink()) : + (cell.shareImageView?.image = imageCache.getImageShared()) } else { - cell.fileSharedImage?.image = imageCache.getImageCanShare() + cell.shareImageView?.image = imageCache.getImageCanShare() } // Button More @@ -347,34 +336,34 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { // Status if metadata.isLivePhoto { - cell.fileStatusImage?.image = utility.loadImage(named: "livephoto", colors: [NCBrandColor.shared.iconImageColor]) + cell.statusImageView?.image = utility.loadImage(named: "livephoto", colors: [NCBrandColor.shared.iconImageColor]) a11yValues.append(NSLocalizedString("_upload_mov_livephoto_", comment: "")) } else if metadata.isVideo { - cell.fileStatusImage?.image = utility.loadImage(named: "play.circle.fill", colors: [.systemBackgroundInverted, .systemGray5]) + cell.statusImageView?.image = utility.loadImage(named: "play.circle.fill", colors: [.systemBackgroundInverted, .systemGray5]) } switch metadata.status { case global.metadataStatusWaitCreateFolder: - cell.fileStatusImage?.image = utility.loadImage(named: "arrow.triangle.2.circlepath", colors: NCBrandColor.shared.iconImageMultiColors) - cell.fileInfoLabel?.text = NSLocalizedString("_status_wait_create_folder_", comment: "") + cell.statusImageView?.image = utility.loadImage(named: "arrow.triangle.2.circlepath", colors: NCBrandColor.shared.iconImageMultiColors) + cell.info?.text = NSLocalizedString("_status_wait_create_folder_", comment: "") case global.metadataStatusWaitFavorite: - cell.fileStatusImage?.image = utility.loadImage(named: "star.circle", colors: NCBrandColor.shared.iconImageMultiColors) - cell.fileInfoLabel?.text = NSLocalizedString("_status_wait_favorite_", comment: "") + cell.statusImageView?.image = utility.loadImage(named: "star.circle", colors: NCBrandColor.shared.iconImageMultiColors) + cell.info?.text = NSLocalizedString("_status_wait_favorite_", comment: "") case global.metadataStatusWaitCopy: - cell.fileStatusImage?.image = utility.loadImage(named: "c.circle", colors: NCBrandColor.shared.iconImageMultiColors) - cell.fileInfoLabel?.text = NSLocalizedString("_status_wait_copy_", comment: "") + cell.statusImageView?.image = utility.loadImage(named: "c.circle", colors: NCBrandColor.shared.iconImageMultiColors) + cell.info?.text = NSLocalizedString("_status_wait_copy_", comment: "") case global.metadataStatusWaitMove: - cell.fileStatusImage?.image = utility.loadImage(named: "m.circle", colors: NCBrandColor.shared.iconImageMultiColors) - cell.fileInfoLabel?.text = NSLocalizedString("_status_wait_move_", comment: "") + cell.statusImageView?.image = utility.loadImage(named: "m.circle", colors: NCBrandColor.shared.iconImageMultiColors) + cell.info?.text = NSLocalizedString("_status_wait_move_", comment: "") case global.metadataStatusWaitRename: - cell.fileStatusImage?.image = utility.loadImage(named: "a.circle", colors: NCBrandColor.shared.iconImageMultiColors) - cell.fileInfoLabel?.text = NSLocalizedString("_status_wait_rename_", comment: "") + cell.statusImageView?.image = utility.loadImage(named: "a.circle", colors: NCBrandColor.shared.iconImageMultiColors) + cell.info?.text = NSLocalizedString("_status_wait_rename_", comment: "") case global.metadataStatusWaitDownload: - cell.fileStatusImage?.image = utility.loadImage(named: "arrow.triangle.2.circlepath", colors: NCBrandColor.shared.iconImageMultiColors) + cell.statusImageView?.image = utility.loadImage(named: "arrow.triangle.2.circlepath", colors: NCBrandColor.shared.iconImageMultiColors) case global.metadataStatusDownloading: - cell.fileStatusImage?.image = utility.loadImage(named: "arrowshape.down.circle", colors: NCBrandColor.shared.iconImageMultiColors) + cell.statusImageView?.image = utility.loadImage(named: "arrowshape.down.circle", colors: NCBrandColor.shared.iconImageMultiColors) case global.metadataStatusDownloadError, global.metadataStatusUploadError: - cell.fileStatusImage?.image = utility.loadImage(named: "exclamationmark.circle", colors: NCBrandColor.shared.iconImageMultiColors) + cell.statusImageView?.image = utility.loadImage(named: "exclamationmark.circle", colors: NCBrandColor.shared.iconImageMultiColors) default: break } @@ -383,17 +372,17 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if !metadata.ownerId.isEmpty, metadata.ownerId != metadata.userId { let fileName = NCSession.shared.getFileName(urlBase: metadata.urlBase, user: metadata.ownerId) if let image = NCImageCache.shared.getImageCache(key: fileName) { - cell.fileAvatarImageView?.contentMode = .scaleAspectFill - cell.fileAvatarImageView?.image = image + cell.avatarImageView?.contentMode = .scaleAspectFill + cell.avatarImageView?.image = image } else { self.database.getImageAvatarLoaded(fileName: fileName) { image, tblAvatar in if let image { - cell.fileAvatarImageView?.contentMode = .scaleAspectFill - cell.fileAvatarImageView?.image = image + cell.avatarImageView?.contentMode = .scaleAspectFill + cell.avatarImageView?.image = image NCImageCache.shared.addImageCache(image: image, key: fileName) } else { - cell.fileAvatarImageView?.contentMode = .scaleAspectFill - cell.fileAvatarImageView?.image = self.utility.loadUserImage(for: metadata.ownerId, displayName: metadata.ownerDisplayName, urlBase: metadata.urlBase) + cell.avatarImageView?.contentMode = .scaleAspectFill + cell.avatarImageView?.image = self.utility.loadUserImage(for: metadata.ownerId, displayName: metadata.ownerDisplayName, urlBase: metadata.urlBase) } if !(tblAvatar?.loaded ?? false), @@ -406,19 +395,16 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { // URL if metadata.classFile == NKTypeClassFile.url.rawValue { - cell.fileLocalImage?.image = nil + cell.localImageView?.image = nil cell.hideButtonShare(true) cell.hideButtonMore(true) - if let ownerId = getAvatarFromIconUrl(metadata: metadata) { - cell.fileUser = ownerId - } } // Separator if collectionView.numberOfItems(inSection: indexPath.section) == indexPath.row + 1 || isSearchingMode { - cell.cellSeparatorView?.isHidden = true + cell.separatorView?.isHidden = true } else { - cell.cellSeparatorView?.isHidden = false + cell.separatorView?.isHidden = false } // Edit mode @@ -430,17 +416,17 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } // Accessibility - cell.setAccessibility(label: metadata.fileNameView + ", " + (cell.fileInfoLabel?.text ?? "") + (cell.fileSubinfoLabel?.text ?? ""), value: a11yValues.joined(separator: ", ")) + cell.setAccessibility(label: metadata.fileNameView + ", " + (cell.info?.text ?? "") + (cell.subInfo?.text ?? ""), value: a11yValues.joined(separator: ", ")) // Color string find in search - cell.fileTitleLabel?.textColor = NCBrandColor.shared.textColor - cell.fileTitleLabel?.font = .systemFont(ofSize: 15) + cell.title?.textColor = NCBrandColor.shared.textColor + cell.title?.font = .systemFont(ofSize: 15) - if isSearchingMode, let literalSearch = self.literalSearch, let title = cell.fileTitleLabel?.text { + if isSearchingMode, let literalSearch = self.literalSearch, let title = cell.title?.text { let longestWordRange = (title.lowercased() as NSString).range(of: literalSearch) let attributedString = NSMutableAttributedString(string: title, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15)]) attributedString.setAttributes([NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 15), NSAttributedString.Key.foregroundColor: UIColor.systemBlue], range: longestWordRange) - cell.fileTitleLabel?.attributedText = attributedString + cell.title?.attributedText = attributedString } // TAGS @@ -457,12 +443,12 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.hideLabelInfo(false) cell.hideLabelSubinfo(false) cell.hideImageStatus(false) - cell.fileTitleLabel?.font = UIFont.systemFont(ofSize: 15) + cell.title?.font = UIFont.systemFont(ofSize: 15) if width < 120 { cell.hideImageFavorite(true) cell.hideImageLocal(true) - cell.fileTitleLabel?.font = UIFont.systemFont(ofSize: 10) + cell.title?.font = UIFont.systemFont(ofSize: 10) if width < 100 { cell.hideImageItem(true) cell.hideButtonMore(true) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewDownloadThumbnail.swift b/iOSClient/Main/Collection Common/NCCollectionViewDownloadThumbnail.swift index eefbcc7d3b..a9651883d1 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewDownloadThumbnail.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewDownloadThumbnail.swift @@ -42,13 +42,13 @@ class NCCollectionViewDownloadThumbnail: ConcurrentOperation, @unchecked Sendabl let image = self.utility.getImage(ocId: self.metadata.ocId, etag: self.metadata.etag, ext: self.ext, userId: self.metadata.userId, urlBase: self.metadata.urlBase) Task { @MainActor in - for case let cell as NCCellProtocol in collectionView.visibleCells where cell.fileOcId == self.metadata.ocId { - if let filePreviewImageView = cell.filePreviewImageView { - filePreviewImageView.contentMode = .scaleAspectFill + for case let cell as NCCellProtocol in collectionView.visibleCells where cell.metadata?.ocId == self.metadata.ocId { + if let previewImageView = cell.previewImageView { + previewImageView.contentMode = .scaleAspectFill if self.metadata.hasPreviewBorder { - filePreviewImageView.layer.borderWidth = 0.2 - filePreviewImageView.layer.borderColor = UIColor.systemGray3.cgColor + previewImageView.layer.borderWidth = 0.2 + previewImageView.layer.borderColor = UIColor.systemGray3.cgColor } if let photoCell = (cell as? NCPhotoCell), @@ -57,11 +57,15 @@ class NCCollectionViewDownloadThumbnail: ConcurrentOperation, @unchecked Sendabl cell.hideImageStatus(false) } - UIView.transition(with: filePreviewImageView, - duration: 0.75, - options: .transitionCrossDissolve, - animations: { filePreviewImageView.image = image }, - completion: nil) + UIView.transition( + with: previewImageView, + duration: 0.75, + options: .transitionCrossDissolve, + animations: { + previewImageView.image = image + }, + completion: nil + ) break } } diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index 6a562ec8a1..bedfb4a1e1 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -186,7 +186,7 @@ extension NCSectionFirstHeader: UICollectionViewDataSource { 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 { + if cell.metadata?.fileId == recommendedFiles.id { cell.image.contentMode = .scaleAspectFill if metadata.classFile == NKTypeClassFile.document.rawValue { cell.setImageCorner(withBorder: true) @@ -216,7 +216,6 @@ extension NCSectionFirstHeader: UICollectionViewDataSource { cell.delegate = self cell.metadata = metadata cell.recommendedFiles = recommendedFiles - cell.id = recommendedFiles.id } return cell @@ -266,10 +265,10 @@ extension NCSectionFirstHeader: UICollectionViewDelegateFlowLayout { } extension NCSectionFirstHeader: NCRecommendationsCellDelegate { - func contextMenu(with metadata: tableMetadata, button: UIButton, sender: Any) { + func contextMenu(with metadata: tableMetadata?, button: UIButton, sender: Any) { #if !EXTENSION Task { - guard let viewController = self.viewController else { + guard let viewController = self.viewController, let metadata else { return } button.menu = NCContextMenu(metadata: metadata, viewController: viewController, sceneIdentifier: self.sceneIdentifier, sender: sender).viewMenu() @@ -277,7 +276,7 @@ extension NCSectionFirstHeader: NCRecommendationsCellDelegate { #endif } - func onMenuIntent(with ocId: String) { + func onMenuIntent(with metadata: tableMetadata?) { #if !EXTENSION Task { let collectionViewCommon = (self.viewController as? NCCollectionViewCommon) diff --git a/iOSClient/Networking/NCNetworking+WebDAV.swift b/iOSClient/Networking/NCNetworking+WebDAV.swift index 9a016c7429..f96b565613 100644 --- a/iOSClient/Networking/NCNetworking+WebDAV.swift +++ b/iOSClient/Networking/NCNetworking+WebDAV.swift @@ -1102,7 +1102,6 @@ class NCOperationDownloadAvatar: ConcurrentOperation, @unchecked Sendable { await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) } } completion: { _, image, _, etag, _, error in - if error == .success, let image { NCManageDatabase.shared.addAvatar(fileName: self.fileName, etag: etag ?? "") #if !EXTENSION @@ -1112,12 +1111,11 @@ class NCOperationDownloadAvatar: ConcurrentOperation, @unchecked Sendable { DispatchQueue.main.async { let visibleCells: [UIView] = (self.view as? UICollectionView)?.visibleCells ?? (self.view as? UITableView)?.visibleCells ?? [] for case let cell as NCCellProtocol in visibleCells { - if self.user == cell.fileUser { - - if self.isPreviewImageView, let filePreviewImageView = cell.filePreviewImageView { - UIView.transition(with: filePreviewImageView, duration: 0.75, options: .transitionCrossDissolve, animations: { filePreviewImageView.image = image}, completion: nil) - } else if let fileAvatarImageView = cell.fileAvatarImageView { - UIView.transition(with: fileAvatarImageView, duration: 0.75, options: .transitionCrossDissolve, animations: { fileAvatarImageView.image = image}, completion: nil) + if self.user == cell.metadata?.ownerId { + if self.isPreviewImageView, let previewImageView = cell.previewImageView { + UIView.transition(with: previewImageView, duration: 0.75, options: .transitionCrossDissolve, animations: { previewImageView.image = image}, completion: nil) + } else if let avatarImageView = cell.avatarImageView { + UIView.transition(with: avatarImageView, duration: 0.75, options: .transitionCrossDissolve, animations: { avatarImageView.image = image}, completion: nil) } break } diff --git a/iOSClient/Notification/NCNotification.swift b/iOSClient/Notification/NCNotification.swift index 00a2662c9e..26b1de2981 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -142,9 +142,9 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) if results.image == nil { - cell.fileAvatarImageView?.image = utility.loadUserImage(for: user, displayName: json["user"]?["name"].string, urlBase: session.urlBase) + cell.avatarImageView?.image = utility.loadUserImage(for: user, displayName: json["user"]?["name"].string, urlBase: session.urlBase) } else { - cell.fileAvatarImageView?.image = results.image + cell.avatarImageView?.image = results.image } if !(results.tblAvatar?.loaded ?? false), @@ -371,7 +371,7 @@ class NCNotificationCell: UITableViewCell, NCCellProtocol { get { return index } set { index = newValue } } - var fileAvatarImageView: UIImageView? { + var avatarImageView: UIImageView? { return avatar } var fileUser: String? { diff --git a/iOSClient/Select/NCSelect.swift b/iOSClient/Select/NCSelect.swift index d28947eb33..15cd58d26b 100644 --- a/iOSClient/Select/NCSelect.swift +++ b/iOSClient/Select/NCSelect.swift @@ -334,12 +334,12 @@ extension NCSelect: UICollectionViewDataSource { // Thumbnail if !metadata.directory { if let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt512, userId: metadata.userId, urlBase: metadata.urlBase) { - (cell as? NCCellProtocol)?.filePreviewImageView?.image = image + (cell as? NCCellProtocol)?.previewImageView?.image = image } else { if metadata.iconName.isEmpty { - (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.shared.getImageFile() + (cell as? NCCellProtocol)?.previewImageView?.image = NCImageCache.shared.getImageFile() } else { - (cell as? NCCellProtocol)?.filePreviewImageView?.image = self.utility.loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) + (cell as? NCCellProtocol)?.previewImageView?.image = self.utility.loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) } if metadata.hasPreview, metadata.status == NCGlobal.shared.metadataStatusNormal { @@ -375,9 +375,7 @@ extension NCSelect: UICollectionViewDataSource { // cell.listCellDelegate = self - cell.fileOcId = metadata.ocId - cell.fileOcIdTransfer = metadata.ocIdTransfer - cell.fileUser = metadata.ownerId + cell.metadata = metadata cell.labelTitle.text = metadata.fileNameView cell.labelTitle.textColor = NCBrandColor.shared.textColor diff --git a/iOSClient/Share/NCShareCommentsCell.swift b/iOSClient/Share/NCShareCommentsCell.swift index 59cdf78c81..9c12503cea 100644 --- a/iOSClient/Share/NCShareCommentsCell.swift +++ b/iOSClient/Share/NCShareCommentsCell.swift @@ -43,7 +43,7 @@ class NCShareCommentsCell: UITableViewCell, NCCellProtocol { get { return index } set { index = newValue } } - var fileAvatarImageView: UIImageView? { + var avatarImageView: UIImageView? { return imageItem } var fileUser: String? { diff --git a/iOSClient/Share/NCShareUserCell.swift b/iOSClient/Share/NCShareUserCell.swift index c749a99b5f..e6b60c8a93 100644 --- a/iOSClient/Share/NCShareUserCell.swift +++ b/iOSClient/Share/NCShareUserCell.swift @@ -46,7 +46,7 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { get { return index } set { index = newValue } } - var fileAvatarImageView: UIImageView? { + var avatarImageView: UIImageView? { return imageItem } var fileUser: String? { @@ -180,7 +180,7 @@ class NCSearchUserDropDownCell: DropDownCell, NCCellProtocol { get { return index } set { index = newValue } } - var fileAvatarImageView: UIImageView? { + var avatarImageView: UIImageView? { return imageItem } var fileUser: String? { From 8ddd7468af0383cdf34db0eff18c5f9c2e47f8a4 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 15 Jan 2026 11:19:22 +0100 Subject: [PATCH 56/63] cleaning code Signed-off-by: Marino Faggiana --- iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift | 4 ---- iOSClient/Main/Collection Common/Cell/NCGridCell.swift | 4 ---- iOSClient/Main/Collection Common/Cell/NCListCell.swift | 6 +----- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift index 7cd62207e9..2bcf73c5a0 100644 --- a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift +++ b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift @@ -17,7 +17,6 @@ protocol NCCellProtocol { var shareImageView: UIImageView? { get set } var separatorView: UIView? { get set } - func titleInfoTrailingDefault() func titleInfoTrailingFull() func writeInfoDateSize(date: NSDate, size: Int64) func setButtonMore(image: UIImage) @@ -25,7 +24,6 @@ protocol NCCellProtocol { 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) @@ -82,7 +80,6 @@ extension NCCellProtocol { set {} } - func titleInfoTrailingDefault() {} func titleInfoTrailingFull() {} func writeInfoDateSize(date: NSDate, size: Int64) {} func setButtonMore(image: UIImage) {} @@ -90,7 +87,6 @@ extension NCCellProtocol { 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) {} diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift index 2182b9e141..ed1cd00f01 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift @@ -134,10 +134,6 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto imageLocal.isHidden = status } - func hideLabelTitle(_ status: Bool) { - labelTitle.isHidden = status - } - func hideLabelInfo(_ status: Bool) { labelInfo.isHidden = status } diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index fb814f3c94..2d355a8854 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -129,7 +129,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto separatorHeightConstraint.constant = 0.5 tag0.text = "" tag1.text = "" - titleInfoTrailingDefault() + titleTrailingConstraint.constant = 90 contentView.bringSubviewToFront(buttonMore) buttonMore.menu = nil @@ -162,10 +162,6 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto titleTrailingConstraint.constant = 10 } - func titleInfoTrailingDefault() { - titleTrailingConstraint.constant = 90 - } - func setButtonMore(image: UIImage) { imageMore.image = image } From c777766c4aea7527fb56047e2c45a38327a9a5ca Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 15 Jan 2026 11:21:20 +0100 Subject: [PATCH 57/63] lint Signed-off-by: Marino Faggiana --- .../Main/Collection Common/Cell/NCCellProtocol.swift | 3 ++- iOSClient/Main/Collection Common/Cell/NCGridCell.swift | 8 ++++++-- iOSClient/Main/Collection Common/Cell/NCListCell.swift | 4 ++-- iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift | 8 ++++++-- .../Collection Common/Cell/NCRecommendationsCell.swift | 6 +++++- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift index 2bcf73c5a0..99ca24292c 100644 --- a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift +++ b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift @@ -5,8 +5,9 @@ import UIKit protocol NCCellProtocol { - var avatarImageView: UIImageView? { get } var metadata: tableMetadata? {get set } + + var avatarImageView: UIImageView? { get } var previewImageView: UIImageView? { get set } var title: UILabel? { get set } var info: UILabel? { get set } diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift index ed1cd00f01..0bb73f7aea 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift @@ -22,10 +22,14 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var imageVisualEffect: UIVisualEffectView! @IBOutlet weak var iconsStackView: UIStackView! - var metadata: tableMetadata? { didSet { delegate?.contextMenu(with: metadata, button: buttonMore, sender: self) /* preconfigure UIMenu with each metadata */ } } - weak var delegate: NCGridCellDelegate? + var metadata: tableMetadata? { + didSet { + delegate?.contextMenu(with: metadata, button: buttonMore, sender: self) /* preconfigure UIMenu with each metadata */ + } + } + var previewImageView: UIImageView? { get { return imageItem } set { imageItem = newValue } diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 2d355a8854..df72a63889 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -32,14 +32,14 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var separatorHeightConstraint: NSLayoutConstraint! @IBOutlet weak var titleTrailingConstraint: NSLayoutConstraint! + weak var delegate: NCListCellDelegate? + var metadata: tableMetadata? { didSet { delegate?.contextMenu(with: metadata, button: buttonMore, sender: self) /* preconfigure UIMenu with each metadata */ } } - weak var delegate: NCListCellDelegate? - var avatarImageView: UIImageView? { return imageShared } diff --git a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift index 617292b934..6580a1f640 100644 --- a/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCPhotoCell.swift @@ -16,10 +16,14 @@ class NCPhotoCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProt @IBOutlet weak var buttonMore: UIButton! @IBOutlet weak var imageVisualEffect: UIVisualEffectView! - var metadata: tableMetadata? { didSet { delegate?.contextMenu(with: metadata, button: buttonMore, sender: self) /* preconfigure UIMenu with each metadata */ } } - weak var delegate: NCPhotoCellDelegate? + var metadata: tableMetadata? { + didSet { + delegate?.contextMenu(with: metadata, button: buttonMore, sender: self) /* preconfigure UIMenu with each metadata */ + } + } + var previewImageView: UIImageView? { get { return imageItem } set { imageItem = newValue } diff --git a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift index 32a484471a..e23fb58b88 100644 --- a/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCRecommendationsCell.swift @@ -18,7 +18,11 @@ class NCRecommendationsCell: UICollectionViewCell, UIGestureRecognizerDelegate { var delegate: NCRecommendationsCellDelegate? var recommendedFiles: tableRecommendedFiles = tableRecommendedFiles() - var metadata: tableMetadata? { didSet { delegate?.contextMenu(with: metadata, button: buttonMore, sender: self) /* preconfigure UIMenu with each metadata */ } } + var metadata: tableMetadata? { + didSet { + delegate?.contextMenu(with: metadata, button: buttonMore, sender: self) /* preconfigure UIMenu with each metadata */ + } + } override func awakeFromNib() { super.awakeFromNib() From c6a65dbefe613ef58e43a10956118c63e1cbdeab Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 15 Jan 2026 12:43:52 +0100 Subject: [PATCH 58/63] cleaning (Lint) Signed-off-by: Marino Faggiana --- iOSClient/Activity/NCActivity.swift | 16 ++++--------- .../Activity/NCActivityTableViewCell.swift | 4 +--- iOSClient/Files/NCFiles.swift | 16 ++++--------- .../GUI/Lucid Banner/ErrorBannerView.swift | 8 +++---- ...ionViewCommon+CollectionViewDelegate.swift | 4 +--- .../NCCollectionViewCommon.swift | 14 +++-------- iOSClient/Main/Create/NCCreate.swift | 12 ++-------- iOSClient/Main/NCDragDrop.swift | 10 +++----- iOSClient/Main/NCPickerViewController.swift | 12 +++------- .../NCNetworking+TransferDelegate.swift | 24 +++++-------------- .../NCPlayer/NCPlayerToolBar.swift | 4 +--- 11 files changed, 32 insertions(+), 92 deletions(-) diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index e1335fe29c..adf92a6dd3 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -84,9 +84,7 @@ class NCActivity: UIViewController, NCSharePagingContent { self.loadComments() } else { Task {@MainActor in - await showErrorBanner(controller: self.tabBarController, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(controller: self.tabBarController, errorDescription: error.errorDescription) } } } @@ -441,9 +439,7 @@ extension NCActivity { self.database.addComments(comments, account: metadata.account, objectId: metadata.fileId) } else if error.errorCode != NCGlobal.shared.errorResourceNotFound { Task {@MainActor in - await showErrorBanner(controller: self.tabBarController, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(controller: self.tabBarController, errorDescription: error.errorDescription) } } @@ -583,9 +579,7 @@ extension NCActivity: NCShareCommentsCellDelegate { self.loadComments() } else { Task {@MainActor in - await showErrorBanner(controller: self.tabBarController, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(controller: self.tabBarController, errorDescription: error.errorDescription) } } } @@ -617,9 +611,7 @@ extension NCActivity: NCShareCommentsCellDelegate { self.loadComments() } else { Task {@MainActor in - await showErrorBanner(controller: self.tabBarController, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(controller: self.tabBarController, errorDescription: error.errorDescription) } } } diff --git a/iOSClient/Activity/NCActivityTableViewCell.swift b/iOSClient/Activity/NCActivityTableViewCell.swift index 1a1f5510f0..fae6553e84 100644 --- a/iOSClient/Activity/NCActivityTableViewCell.swift +++ b/iOSClient/Activity/NCActivityTableViewCell.swift @@ -86,9 +86,7 @@ extension NCActivityTableViewCell: UICollectionViewDelegate { } else { let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_trash_file_not_found_") Task {@MainActor in - await showErrorBanner(controller: viewController.controller, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(controller: viewController.controller, errorDescription: error.errorDescription) } } } diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index 2a443ab17d..fdfafa3923 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -321,16 +321,12 @@ class NCFiles: NCCollectionViewCommon { NCContentPresenter().showInfo(description: "Metadata not found") let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, account: account) if error != .success { - await showErrorBanner(controller: self.controller, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(controller: self.controller, errorDescription: error.errorDescription) } } else { // show error Task {@MainActor in - await showErrorBanner(controller: self.controller, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(controller: self.controller, errorDescription: error.errorDescription) } } @@ -350,9 +346,7 @@ class NCFiles: NCCollectionViewCommon { let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, updateVersionV1V2: true, account: account) if error != .success { Task {@MainActor in - await showErrorBanner(controller: self.controller, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(controller: self.controller, errorDescription: error.errorDescription) } } NCActivityIndicator.shared.stop() @@ -361,9 +355,7 @@ class NCFiles: NCCollectionViewCommon { // Client Diagnostic await self.database.addDiagnosticAsync(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors) Task {@MainActor in - await showErrorBanner(controller: self.controller, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(controller: self.controller, errorDescription: error.errorDescription) } } diff --git a/iOSClient/GUI/Lucid Banner/ErrorBannerView.swift b/iOSClient/GUI/Lucid Banner/ErrorBannerView.swift index 9cf26ee1de..a22fc906d6 100644 --- a/iOSClient/GUI/Lucid Banner/ErrorBannerView.swift +++ b/iOSClient/GUI/Lucid Banner/ErrorBannerView.swift @@ -6,13 +6,13 @@ import SwiftUI import LucidBanner @MainActor -func showErrorBanner(controller: UITabBarController?, errorDescription: String, errorCode: Int, sleepBefore: Double = 1) async { +func showErrorBanner(controller: UITabBarController?, errorDescription: String, footnote: String? = nil, sleepBefore: Double = 1) async { let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene - await showErrorBanner(scene: scene, errorDescription: errorDescription, errorCode: errorCode, sleepBefore: sleepBefore) + await showErrorBanner(scene: scene, errorDescription: errorDescription, footnote: footnote, sleepBefore: sleepBefore) } @MainActor -func showErrorBanner(scene: UIWindowScene?, errorDescription: String, errorCode: Int, sleepBefore: Double = 1) async { +func showErrorBanner(scene: UIWindowScene?, errorDescription: String, footnote: String? = nil, sleepBefore: Double = 1) async { try? await Task.sleep(nanoseconds: UInt64(sleepBefore * 1e9)) var scene = scene if scene == nil { @@ -22,7 +22,7 @@ func showErrorBanner(scene: UIWindowScene?, errorDescription: String, errorCode: LucidBanner.shared.show( scene: scene, subtitle: errorDescription, - footnote: "(Code: \(errorCode))", + footnote: footnote, vPosition: .top, autoDismissAfter: NCGlobal.shared.dismissAfterSecond, swipeToDismiss: true, diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift index ee61deff9a..6c2499114f 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift @@ -61,9 +61,7 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { if results.nkError == .success || results.afError?.isExplicitlyCancelledError ?? false { print("ok") } else { - await showErrorBanner(scene: scene, - errorDescription: results.nkError.errorDescription, - errorCode: results.nkError.errorCode) + await showErrorBanner(scene: scene, errorDescription: results.nkError.errorDescription) } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index da170aa7f1..db710613e8 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -624,9 +624,7 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, } } else { Task {@MainActor in - await showErrorBanner(scene: scene, - errorDescription: resultsUpload.error.errorDescription, - errorCode: resultsUpload.error.errorCode) + await showErrorBanner(scene: scene, errorDescription: resultsUpload.error.errorDescription) } } } @@ -747,11 +745,7 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, } completion: { _, searchResult, metadatas, error in if error != .success { Task {@MainActor in - await showErrorBanner( - controller: self.controller, - errorDescription: error.errorDescription, - errorCode: error.errorCode - ) + await showErrorBanner(controller: self.controller, errorDescription: error.errorDescription) } } @@ -922,9 +916,7 @@ extension NCCollectionViewCommon: NCTransferDelegate { Task { if error != .success, error.errorCode != global.errorResourceNotFound { - await showErrorBanner(controller: self.controller, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(controller: self.controller, errorDescription: error.errorDescription) } guard session.account == account else { return diff --git a/iOSClient/Main/Create/NCCreate.swift b/iOSClient/Main/Create/NCCreate.swift index 0348071348..26b6de23a6 100644 --- a/iOSClient/Main/Create/NCCreate.swift +++ b/iOSClient/Main/Create/NCCreate.swift @@ -41,11 +41,7 @@ class NCCreate: NSObject { } guard results.error == .success, let url = results.url else { Task {@MainActor in - await showErrorBanner( - controller: controller, - errorDescription: results.error.errorDescription, - errorCode: results.error.errorCode - ) + await showErrorBanner(controller: controller, errorDescription: results.error.errorDescription) } return } @@ -72,11 +68,7 @@ class NCCreate: NSObject { } guard results.error == .success, let url = results.url else { Task {@MainActor in - await showErrorBanner( - controller: controller, - errorDescription: results.error.errorDescription, - errorCode: results.error.errorCode - ) + await showErrorBanner(controller: controller, errorDescription: results.error.errorDescription) } return } diff --git a/iOSClient/Main/NCDragDrop.swift b/iOSClient/Main/NCDragDrop.swift index 24f0545624..9fe6217710 100644 --- a/iOSClient/Main/NCDragDrop.swift +++ b/iOSClient/Main/NCDragDrop.swift @@ -145,11 +145,7 @@ class NCDragDrop: NSObject { } catch { Task {@MainActor in let error = NKError(error: error) - await showErrorBanner( - controller: controller, - errorDescription: error.errorDescription, - errorCode: error.errorCode - ) + await showErrorBanner(controller: controller, errorDescription: error.errorDescription) } return } @@ -230,7 +226,7 @@ class NCDragDrop: NSObject { downloadRequest = request } guard results.nkError == .success else { - await showErrorBanner(scene: scene, errorDescription: results.nkError.errorDescription, errorCode: results.nkError.errorCode) + await showErrorBanner(scene: scene, errorDescription: results.nkError.errorDescription) break } } @@ -252,7 +248,7 @@ class NCDragDrop: NSObject { uploadRequest = request } guard results.error == .success else { - await showErrorBanner(scene: scene, errorDescription: results.error.errorDescription, errorCode: results.error.errorCode) + await showErrorBanner(scene: scene, errorDescription: results.error.errorDescription) break } diff --git a/iOSClient/Main/NCPickerViewController.swift b/iOSClient/Main/NCPickerViewController.swift index a44ccfdd8c..a6e20c3411 100644 --- a/iOSClient/Main/NCPickerViewController.swift +++ b/iOSClient/Main/NCPickerViewController.swift @@ -61,27 +61,21 @@ class NCPhotosPickerViewController: NSObject { pickerVC?.didExceedMaximumNumberOfSelection = { _ in let error = NKError(errorCode: self.global.errorInternalError, errorDescription: "_limited_dimension_") Task {@MainActor in - await showErrorBanner(controller: self.controller, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(controller: self.controller, errorDescription: error.errorDescription) } } pickerVC?.handleNoAlbumPermissions = { _ in let error = NKError(errorCode: self.global.errorInternalError, errorDescription: "_denied_album_") Task {@MainActor in - await showErrorBanner(controller: self.controller, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(controller: self.controller, errorDescription: error.errorDescription) } } pickerVC?.handleNoCameraPermissions = { _ in let error = NKError(errorCode: self.global.errorInternalError, errorDescription: "_denied_camera_") Task {@MainActor in - await showErrorBanner(controller: self.controller, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(controller: self.controller, errorDescription: error.errorDescription) } } diff --git a/iOSClient/Networking/NCNetworking+TransferDelegate.swift b/iOSClient/Networking/NCNetworking+TransferDelegate.swift index d3000ffaef..87e113ddff 100644 --- a/iOSClient/Networking/NCNetworking+TransferDelegate.swift +++ b/iOSClient/Networking/NCNetworking+TransferDelegate.swift @@ -122,9 +122,7 @@ extension NCNetworking: NCTransferDelegate { guard hasPermission else { Task {@MainActor in let error = NKError(errorCode: NCGlobal.shared.errorFileNotSaved, errorDescription: "_access_photo_not_enabled_msg_") - await showErrorBanner(scene: scene, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(scene: scene, errorDescription: error.errorDescription) } return } @@ -140,9 +138,7 @@ extension NCNetworking: NCTransferDelegate { }) { success, _ in if !success { Task {@MainActor in - await showErrorBanner(scene: scene, - errorDescription: errorSave.errorDescription, - errorCode: errorSave.errorCode) + await showErrorBanner(scene: scene, errorDescription: errorSave.errorDescription) } } } @@ -152,25 +148,19 @@ extension NCNetworking: NCTransferDelegate { }) { success, _ in if !success { Task {@MainActor in - await showErrorBanner(scene: scene, - errorDescription: errorSave.errorDescription, - errorCode: errorSave.errorCode) + await showErrorBanner(scene: scene, errorDescription: errorSave.errorDescription) } } } } else { Task {@MainActor in - await showErrorBanner(scene: scene, - errorDescription: errorSave.errorDescription, - errorCode: errorSave.errorCode) + await showErrorBanner(scene: scene, errorDescription: errorSave.errorDescription) } return } } catch { Task {@MainActor in - await showErrorBanner(scene: scene, - errorDescription: errorSave.errorDescription, - errorCode: errorSave.errorCode) + await showErrorBanner(scene: scene, errorDescription: errorSave.errorDescription) } } } @@ -240,9 +230,7 @@ extension NCNetworking: NCTransferDelegate { } guard resultsFile.error == .success, let file = resultsFile.file else { Task {@MainActor in - await showErrorBanner(controller: viewController.tabBarController, - errorDescription: resultsFile.error.errorDescription, - errorCode: resultsFile.error.errorCode) + await showErrorBanner(controller: viewController.tabBarController, errorDescription: resultsFile.error.errorDescription) } return } diff --git a/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift b/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift index ccb930df56..2d0096e767 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift @@ -492,9 +492,7 @@ extension NCPlayerToolBar: NCSelectDelegate { if error == .success { self.addPlaybackSlave(type: type, metadata: metadata) } else if error.errorCode != 200 { - await showErrorBanner(scene: scene, - errorDescription: error.errorDescription, - errorCode: error.errorCode) + await showErrorBanner(scene: scene, errorDescription: error.errorDescription) } } } From a1cb0f9597145f4b81974362ae3ffa1b99580d21 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 15 Jan 2026 12:45:16 +0100 Subject: [PATCH 59/63] lint Signed-off-by: Marino Faggiana --- iOSClient/Media/NCMedia+CollectionViewDelegate.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOSClient/Media/NCMedia+CollectionViewDelegate.swift b/iOSClient/Media/NCMedia+CollectionViewDelegate.swift index 90da5a609b..c5113c7ab4 100644 --- a/iOSClient/Media/NCMedia+CollectionViewDelegate.swift +++ b/iOSClient/Media/NCMedia+CollectionViewDelegate.swift @@ -44,7 +44,6 @@ extension NCMedia: UICollectionViewDelegate { return UIContextMenuConfiguration(identifier: identifier, previewProvider: { return NCViewerProviderContextMenu(metadata: metadata, image: image, sceneIdentifier: self.sceneIdentifier) }, actionProvider: { _ in - let cell = collectionView.cellForItem(at: indexPath) let contextMenu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, sender: collectionView) return contextMenu.viewMenu() }) From a87d76a488a8837398a0436e23c9c357f70307cd Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 15 Jan 2026 15:02:41 +0100 Subject: [PATCH 60/63] NCContextMenu Signed-off-by: Marino Faggiana --- iOSClient/Data/NCManageDatabase+Metadata.swift | 7 +++++++ ...tionViewCommon+CollectionViewDataSource.swift | 14 ++++++++++---- iOSClient/Menu/NCContextMenu.swift | 16 ++++------------ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index f03ef6cf8d..b4b8615114 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -125,6 +125,13 @@ class tableMetadata: Object { @objc dynamic var autoUploadServerUrlBase: String? @objc dynamic var typeIdentifier: String = "" + // ========================= + // UI / transient properties + // ========================= + + /// Used only for UI state (not persisted, not observed by Realm) + var isOffline: Bool = false + override static func primaryKey() -> String { return "ocId" } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift index 6b799ff6f5..be4595a357 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift @@ -177,8 +177,6 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { isMounted = metadata.permissions.contains(NCMetadataPermissions.permissionMounted) && !metadataFolder!.permissions.contains(NCMetadataPermissions.permissionMounted) } - cell.metadata = metadata - if isSearchingMode { if metadata.name == global.appName { cell.info?.text = NSLocalizedString("_in_", comment: "") + " " + utilityFileSystem.getPath(path: metadata.path, user: metadata.user) @@ -226,7 +224,9 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } // Local image: offline - if let tblDirectory, tblDirectory.offline { + metadata.isOffline = tblDirectory?.offline ?? false + + if metadata.isOffline { cell.localImageView?.image = imageCache.getImageOfflineFlag(colors: [.systemBackground, .systemGreen]) } @@ -301,7 +301,10 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { } } - if let tableLocalFile, tableLocalFile.offline { + // Local image: offline + metadata.isOffline = tableLocalFile?.offline ?? false + + if metadata.isOffline { a11yValues.append(NSLocalizedString("_offline_", comment: "")) cell.localImageView?.image = imageCache.getImageOfflineFlag(colors: [.systemBackground, .systemGreen]) } else if utilityFileSystem.fileProviderStorageExists(metadata) { @@ -468,6 +471,9 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.setIconOutlines() + // Obligatory here, at the end !! + cell.metadata = metadata + return cell } diff --git a/iOSClient/Menu/NCContextMenu.swift b/iOSClient/Menu/NCContextMenu.swift index 8bdb17c7a9..3f3413282b 100644 --- a/iOSClient/Menu/NCContextMenu.swift +++ b/iOSClient/Menu/NCContextMenu.swift @@ -31,16 +31,10 @@ class NCContextMenu: NSObject { } func viewMenu() -> UIMenu { - let database = NCManageDatabase.shared - - guard let metadata = database.getMetadataFromOcId(metadata.ocId), - let capabilities = NCNetworking.shared.capabilities[metadata.account] else { + guard let capabilities = NCNetworking.shared.capabilities[metadata.account] else { return UIMenu() } - let localFile = database.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) - let isOffline = localFile?.offline == true - // Build top menu items let detail = makeDetailAction(metadata: metadata) let favorite = makeFavoriteAction(metadata: metadata) @@ -48,8 +42,7 @@ class NCContextMenu: NSObject { let mainActionsMenu = buildMainActionsMenu( metadata: metadata, - capabilities: capabilities, - isOffline: isOffline + capabilities: capabilities ) let clientIntegrationMenu = buildClientIntegrationMenuItems( @@ -125,8 +118,7 @@ class NCContextMenu: NSObject { private func buildMainActionsMenu( metadata: tableMetadata, - capabilities: NKCapabilities.Capabilities, - isOffline: Bool + capabilities: NKCapabilities.Capabilities ) -> [UIMenuElement] { var mainActionsMenu: [UIMenuElement] = [] // Lock/Unlock @@ -148,7 +140,7 @@ class NCContextMenu: NSObject { mainActionsMenu.append( ContextMenuActions.setAvailableOffline( selectedMetadatas: [metadata], - isAnyOffline: isOffline, + isAnyOffline: metadata.isOffline, viewController: viewController ) ) From 63c835f55af48ae571a45aafba1bbac81b5c7e6b Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 15 Jan 2026 15:18:32 +0100 Subject: [PATCH 61/63] NCContextMenu Signed-off-by: Marino Faggiana --- iOSClient/Menu/NCContextMenu.swift | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/iOSClient/Menu/NCContextMenu.swift b/iOSClient/Menu/NCContextMenu.swift index 3f3413282b..698d766d9e 100644 --- a/iOSClient/Menu/NCContextMenu.swift +++ b/iOSClient/Menu/NCContextMenu.swift @@ -14,9 +14,6 @@ import LucidBanner class NCContextMenu: NSObject { let utilityFileSystem = NCUtilityFileSystem() let utility = NCUtility() - let database = NCManageDatabase.shared - let global = NCGlobal.shared - let networking = NCNetworking.shared let metadata: tableMetadata let sceneIdentifier: String @@ -53,7 +50,7 @@ class NCContextMenu: NSObject { let deleteMenu = buildDeleteMenu(metadata: metadata) // Assemble final menu - if self.networking.isOnline { + if NCNetworking.shared.isOnline { let baseChildren = [ UIMenu(title: "", options: .displayInline, children: mainActionsMenu), UIMenu(title: "", options: .displayInline, children: clientIntegrationMenu), @@ -90,7 +87,7 @@ class NCContextMenu: NSObject { colors: [NCBrandColor.shared.yellowFavorite] ) ) { _ in - self.networking.setStatusWaitFavorite(metadata) { error in + NCNetworking.shared.setStatusWaitFavorite(metadata) { error in if error != .success { NCContentPresenter().showError(error: error) } @@ -196,7 +193,7 @@ class NCContextMenu: NSObject { metadata.size == 0, !metadata.e2eEncrypted, NCPreferences().isEndToEndEnabled(account: metadata.account), - metadata.serverUrl == NCUtilityFileSystem().getHomeServer(urlBase: metadata.urlBase, userId: metadata.userId) { + metadata.serverUrl == self.utilityFileSystem.getHomeServer(urlBase: metadata.urlBase, userId: metadata.userId) { mainActionsMenu.append(makeSetFolderE2EEAction(metadata: metadata)) } @@ -247,14 +244,14 @@ class NCContextMenu: NSObject { } if results.error == .success { - await self.database.deleteE2eEncryptionAsync( + await NCManageDatabase.shared.deleteE2eEncryptionAsync( predicate: NSPredicate( format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrlFileName ) ) - await self.database.setMetadataEncryptedAsync(ocId: metadata.ocId, encrypted: false) + await NCManageDatabase.shared.setMetadataEncryptedAsync(ocId: metadata.ocId, encrypted: false) await (self.viewController as? NCCollectionViewCommon)?.reloadDataSource() } else { NCContentPresenter().messageNotification( @@ -290,7 +287,7 @@ class NCContextMenu: NSObject { ) } } else { - if let metadata = await self.database.setMetadataSessionInWaitDownloadAsync( + if let metadata = await NCManageDatabase.shared.setMetadataSessionInWaitDownloadAsync( ocId: metadata.ocId, session: NCNetworking.shared.sessionDownload, selector: NCGlobal.shared.selectorSaveAsScan, @@ -357,7 +354,7 @@ class NCContextMenu: NSObject { ) } } else { - if let metadata = await self.database.setMetadataSessionInWaitDownloadAsync( + if let metadata = await NCManageDatabase.shared.setMetadataSessionInWaitDownloadAsync( ocId: metadata.ocId, session: NCNetworking.shared.sessionDownload, selector: NCGlobal.shared.selectorLoadFileQuickLook, @@ -429,7 +426,7 @@ class NCContextMenu: NSObject { ) { _ in if let viewController = self.viewController as? NCCollectionViewCommon { Task { - await self.networking.setStatusWaitDelete( + await NCNetworking.shared.setStatusWaitDelete( metadatas: [metadata], sceneIdentifier: self.sceneIdentifier ) @@ -450,12 +447,12 @@ class NCContextMenu: NSObject { attributes: .destructive ) { _ in Task { - let error = await self.networking.deleteCache( + let error = await NCNetworking.shared.deleteCache( metadata, sceneIdentifier: self.sceneIdentifier ) - await self.networking.transferDispatcher.notifyAllDelegates { delegate in + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in delegate.transferChange( status: NCGlobal.shared.networkingStatusDelete, account: metadata.account, From 7aeadcc36dd888f6b2135889d066876447cfd8b2 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 16 Jan 2026 08:30:46 +0100 Subject: [PATCH 62/63] doc Signed-off-by: Marino Faggiana --- iOSClient/main.swift | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/iOSClient/main.swift b/iOSClient/main.swift index e412f98b82..bf621bd0f4 100644 --- a/iOSClient/main.swift +++ b/iOSClient/main.swift @@ -4,6 +4,11 @@ import UIKit +/// Entry point of the application. +/// +/// This call bootstraps the UIKit application using a custom `UIApplication` +/// subclass (`NCApplication`) in order to intercept and observe low-level +/// user interaction events globally. UIApplicationMain( CommandLine.argc, CommandLine.unsafeArgv, @@ -11,18 +16,55 @@ UIApplicationMain( NSStringFromClass(AppDelegate.self) ) +/// Custom `UIApplication` subclass used to intercept all UIEvents +/// before they are dispatched to windows, scenes, and the view hierarchy. +/// +/// This is the lowest and most reliable interception point in UIKit for +/// observing user interactions such as touches, presses, and motion events. +/// The implementation forwards events to `UserInteractionMonitor` while +/// preserving default UIKit behavior final class NCApplication: UIApplication { + /// Intercepts and forwards every UIEvent dispatched by the application. + /// + /// - Parameter event: The UIEvent generated by the system. + /// + /// This method must always call `super.sendEvent(_:)` to ensure + /// normal event delivery. It is intentionally lightweight and delegates + /// all processing to `UserInteractionMonitor` override func sendEvent(_ event: UIEvent) { super.sendEvent(event) UserInteractionMonitor.shared.handle(event: event) } } +/// Centralized monitor for observing user interaction activity. +/// +/// This class listens for low-level UIEvents and emits a notification +/// when a complete user interaction cycle has finished (i.e. all touches +/// have ended or been cancelled). +/// +/// Typical use cases include: +/// - inactivity or idle detection +/// - global debouncing or refresh triggers +/// - analytics or telemetry +/// - security auto-lock timers +/// +/// The monitor is intentionally decoupled from UI components and communicates +/// exclusively via NotificationCenter final class UserInteractionMonitor { static let shared = UserInteractionMonitor() private init() {} + /// Handles a UIEvent forwarded by the application. + /// + /// - Parameter event: The UIEvent received from `UIApplication.sendEvent(_:)`. + /// + /// Only touch events are considered. When all touches associated with the + /// event are either `.ended` or `.cancelled`, a notification is posted + /// indicating that a user interaction cycle has completed. + /// + /// The notification name is defined by `NCGlobal.shared.notificationCenterUserInteractionMonitor` func handle(event: UIEvent) { guard event.type == .touches else { return } guard let touches = event.allTouches, !touches.isEmpty else { return } From 79ee4681639bf386604b7e0fbcd74cb2b2ebd637 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 16 Jan 2026 08:48:43 +0100 Subject: [PATCH 63/63] code Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 32 +++++++++---------- ...+Helper.swift => NCNetworking+Actor.swift} | 20 ------------ iOSClient/Networking/NCNetworking.swift | 22 ++++++++++++- 3 files changed, 37 insertions(+), 37 deletions(-) rename iOSClient/Networking/{NCNetworking+Helper.swift => NCNetworking+Actor.swift} (90%) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 90693af197..cc39c7ec55 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -755,13 +755,13 @@ F7CAFE182F164B9500DB35A5 /* NCCollectionViewCommon+CellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE172F164B9200DB35A5 /* NCCollectionViewCommon+CellDelegate.swift */; }; F7CAFE192F168F6000DB35A5 /* NCDebouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A3DB8F2DDE238C008F7EC8 /* NCDebouncer.swift */; }; F7CAFE1B2F16AA8D00DB35A5 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1A2F16AA8600DB35A5 /* main.swift */; }; - F7CAFE1D2F17A35F00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; - F7CAFE1E2F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; - F7CAFE1F2F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; - F7CAFE202F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; - F7CAFE212F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; - F7CAFE222F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; - F7CAFE232F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */; }; + F7CAFE1D2F17A35F00DB35A5 /* NCNetworking+Actor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Actor.swift */; }; + F7CAFE1E2F17A37C00DB35A5 /* NCNetworking+Actor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Actor.swift */; }; + F7CAFE1F2F17A37C00DB35A5 /* NCNetworking+Actor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Actor.swift */; }; + F7CAFE202F17A37C00DB35A5 /* NCNetworking+Actor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Actor.swift */; }; + F7CAFE212F17A37C00DB35A5 /* NCNetworking+Actor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Actor.swift */; }; + F7CAFE222F17A37C00DB35A5 /* NCNetworking+Actor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Actor.swift */; }; + F7CAFE232F17A37C00DB35A5 /* NCNetworking+Actor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Actor.swift */; }; F7CB689A2541676B0050EC94 /* NCMore.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7CB68992541676B0050EC94 /* NCMore.storyboard */; }; F7CBC1232BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */; }; F7CBC1242BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */; }; @@ -1698,7 +1698,7 @@ F7CADEFA2EA1591D0057849E /* NCMetadataTranfersSuccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMetadataTranfersSuccess.swift; sourceTree = ""; }; F7CAFE172F164B9200DB35A5 /* NCCollectionViewCommon+CellDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+CellDelegate.swift"; sourceTree = ""; }; F7CAFE1A2F16AA8600DB35A5 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; - F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCNetworking+Helper.swift"; sourceTree = ""; }; + F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Actor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCNetworking+Actor.swift"; sourceTree = ""; }; F7CB68992541676B0050EC94 /* NCMore.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCMore.storyboard; sourceTree = ""; }; F7CBC1212BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCSectionFirstHeaderEmptyData.xib; sourceTree = ""; }; F7CBC1222BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCSectionFirstHeaderEmptyData.swift; sourceTree = ""; }; @@ -2418,7 +2418,7 @@ F72CD63925C19EBF00F46F9A /* NCAutoUpload.swift */, F77BC3EC293E528A005F2B08 /* NCConfigServer.swift */, F75A9EE523796C6F0044CFCE /* NCNetworking.swift */, - F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Helper.swift */, + F7CAFE1C2F17A34F00DB35A5 /* NCNetworking+Actor.swift */, F70898662EDDB39300EF85BD /* NCNetworking+TransferDelegate.swift */, F76341172EBE0BB80056F538 /* NCNetworking+NextcloudKitDelegate.swift */, F7327E1F2B73A42F00A462C7 /* NCNetworking+Download.swift */, @@ -4093,7 +4093,7 @@ F798F0EC2588060A000DAFFD /* UIColor+Extension.swift in Sources */, F76882372C0DD22F001CF441 /* NCPreferences.swift in Sources */, F73EF7E52B02266D0087E6E9 /* NCManageDatabase+Trash.swift in Sources */, - F7CAFE232F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */, + F7CAFE232F17A37C00DB35A5 /* NCNetworking+Actor.swift in Sources */, F71F6D0D2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */, F763D2A32A249C4500A3C901 /* NCManageDatabase+Capabilities.swift in Sources */, F749B656297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */, @@ -4161,7 +4161,7 @@ F7F1FB9E2E27CE7200C79E20 /* NCNetworking.swift in Sources */, F77DD6AD2C5CC093009448FB /* NCSession.swift in Sources */, F76340F92EBDE9760056F538 /* NCManageDatabaseCore.swift in Sources */, - F7CAFE222F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */, + F7CAFE222F17A37C00DB35A5 /* NCNetworking+Actor.swift in Sources */, F7E742F42EC0A10C00E2362A /* NCManageDatabase+Account.swift in Sources */, F763410B2EBDFCB10056F538 /* NCManageDatabase+CreateMetadata.swift in Sources */, F7490E6B29882A92009DCE94 /* NCGlobal.swift in Sources */, @@ -4200,7 +4200,7 @@ F74B6D982A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */, F7CF06872E1127460063AD04 /* NCManageDatabase+CreateMetadata.swift in Sources */, F7FDFF722E437E55000D7688 /* NCAccountRequest.swift in Sources */, - F7CAFE202F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */, + F7CAFE202F17A37C00DB35A5 /* NCNetworking+Actor.swift in Sources */, F7707687263A853700A1BA94 /* NCContentPresenter.swift in Sources */, F343A4B62A1E084200DDA874 /* PHAsset+Extension.swift in Sources */, F70460532499095400BB98A7 /* NotificationCenter+MainThread.swift in Sources */, @@ -4311,7 +4311,7 @@ F78302F928B4C3E600B84583 /* NCManageDatabase+Account.swift in Sources */, F7E0710128B13BB00001B882 /* DashboardData.swift in Sources */, F783030328B4C4DD00B84583 /* ThreadSafeDictionary.swift in Sources */, - F7CAFE1E2F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */, + F7CAFE1E2F17A37C00DB35A5 /* NCNetworking+Actor.swift in Sources */, F77ED59128C9CE9D00E24ED0 /* ToolbarData.swift in Sources */, F78302F728B4C3C900B84583 /* NCManageDatabase.swift in Sources */, F7346E1628B0EF5C006CE2D2 /* Widget.swift in Sources */, @@ -4393,7 +4393,7 @@ F3E173C42C9B1067006D177A /* AwakeMode.swift in Sources */, F7D61E932EBF1366007F865B /* UIColor+Extension.swift in Sources */, F76340F42EBDE9760056F538 /* NCManageDatabaseCore.swift in Sources */, - F7CAFE212F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */, + F7CAFE212F17A37C00DB35A5 /* NCNetworking+Actor.swift in Sources */, F76340EE2EBDE74C0056F538 /* NCManageDatabase.swift in Sources */, F763410A2EBDFCB10056F538 /* NCManageDatabase+CreateMetadata.swift in Sources */, F7E742F72EC0A4CD00E2362A /* NCManageDatabase+LocalFile.swift in Sources */, @@ -4432,7 +4432,7 @@ 370D26AF248A3D7A00121797 /* NCCellProtocol.swift in Sources */, F32FADA92D1176E3007035E2 /* UIButton+Extension.swift in Sources */, F768822C2C0DD1E7001CF441 /* NCPreferences.swift in Sources */, - F7CAFE1D2F17A35F00DB35A5 /* NCNetworking+Helper.swift in Sources */, + F7CAFE1D2F17A35F00DB35A5 /* NCNetworking+Actor.swift in Sources */, F71CD6CA2930D7B1006C95C1 /* NCApplicationHandle.swift in Sources */, F3754A7D2CF87D600009312E /* SetupPasscodeView.swift in Sources */, F73EF7D72B0226080087E6E9 /* NCManageDatabase+Tip.swift in Sources */, @@ -4793,7 +4793,7 @@ AA8D31532D41052300FE2775 /* NCManageDatabase+DownloadLimit.swift in Sources */, F7A8D74228F18261008BBE1C /* NCUtility.swift in Sources */, F7A8D73A28F17E28008BBE1C /* NCManageDatabase+Video.swift in Sources */, - F7CAFE1F2F17A37C00DB35A5 /* NCNetworking+Helper.swift in Sources */, + F7CAFE1F2F17A37C00DB35A5 /* NCNetworking+Actor.swift in Sources */, F7D61EA72EBF1694007F865B /* NCManageDatabase+TableCapabilities.swift in Sources */, F7A8D73828F17E21008BBE1C /* NCManageDatabase+DashboardWidget.swift in Sources */, F7CF06852E1127460063AD04 /* NCManageDatabase+CreateMetadata.swift in Sources */, diff --git a/iOSClient/Networking/NCNetworking+Helper.swift b/iOSClient/Networking/NCNetworking+Actor.swift similarity index 90% rename from iOSClient/Networking/NCNetworking+Helper.swift rename to iOSClient/Networking/NCNetworking+Actor.swift index 71a0573055..e262af03a8 100644 --- a/iOSClient/Networking/NCNetworking+Helper.swift +++ b/iOSClient/Networking/NCNetworking+Actor.swift @@ -6,26 +6,6 @@ import UIKit import NextcloudKit import Alamofire -protocol NCTransferDelegate: AnyObject { - var sceneIdentifier: String { get } - - func transferChange(status: String, - account: String, - fileName: String, - serverUrl: String, - selector: String?, - ocId: String, - destination: String?, - error: NKError) - func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) - func transferReloadData(serverUrl: String?) - func transferProgressDidUpdate(progress: Float, - totalBytes: Int64, - totalBytesExpected: Int64, - fileName: String, - serverUrl: String) -} - /// Actor-based dispatcher that manages weak NCTransferDelegate references /// and delivers notifications safely across concurrency domains. actor NCTransferDelegateDispatcher { diff --git a/iOSClient/Networking/NCNetworking.swift b/iOSClient/Networking/NCNetworking.swift index e5c2611c06..226efc5e2c 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -12,11 +12,31 @@ import UIKit import NextcloudKit import Alamofire -@objc protocol ClientCertificateDelegate { +protocol ClientCertificateDelegate: AnyObject { func onIncorrectPassword() func didAskForClientCertificate() } +protocol NCTransferDelegate: AnyObject { + var sceneIdentifier: String { get } + + func transferChange(status: String, + account: String, + fileName: String, + serverUrl: String, + selector: String?, + ocId: String, + destination: String?, + error: NKError) + func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) + func transferReloadData(serverUrl: String?) + func transferProgressDidUpdate(progress: Float, + totalBytes: Int64, + totalBytesExpected: Int64, + fileName: String, + serverUrl: String) +} + class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { static let shared = NCNetworking()