From b3e4977d3b67026d9025116c6334bb6a40bb72eb Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 10 Feb 2026 15:39:44 +0100 Subject: [PATCH 1/5] code Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 2 +- iOSClient/Files/NCFiles.swift | 3 ++ .../NCCollectionViewCommon+Search.swift | 51 ++++++------------- .../NCCollectionViewCommon.swift | 27 ++++++++-- 4 files changed, 42 insertions(+), 41 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 638e4d6de0..b325871162 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -6122,7 +6122,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/nextcloud/NextcloudKit"; requirement = { - branch = main; + branch = datarequest; kind = branch; }; }; diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index 1285c12e85..36109db888 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -44,6 +44,9 @@ class NCFiles: NCCollectionViewCommon { NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in self.stopSyncMetadata() + Task { + await self.searchOperationHandle.cancel() + } } if self.serverUrl.isEmpty { diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift index 066bcb83f7..be2e7a2e20 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift @@ -4,6 +4,7 @@ import Foundation import NextcloudKit +import Alamofire extension NCCollectionViewCommon { @MainActor @@ -112,24 +113,19 @@ extension NCCollectionViewCommon { self.collectionView.reloadData() // ---> Get providers - let results = await NextcloudKit.shared.unifiedSearchProviders(account: session.account) { _ in + let results = await NextcloudKit.shared.unifiedSearchProviders(account: session.account, handle: searchOperationHandle) { _ in // example filter // ["calendar", "files", "fulltextsearch"].contains(provider.id) return true - } taskHandler: { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier( - account: self.session.account, - name: "unifiedSearchProviders" - ) - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - self.searchTask = task - self.collectionView.reloadData() - } } if results.error != .success { - await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) + // If Alamofire reported an explicit cancellation, skip showing an error banner + if let afError = results.error.error as? AFError, case .explicitlyCancelled = afError { + // Silently ignore cancellation + } else { + await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) + } } guard isSearchingMode, @@ -154,18 +150,9 @@ extension NCCollectionViewCommon { limit: 5, cursor: 0, timeout: 90, - account: session.account - ) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier( - account: self.session.account, - name: "unifiedSearch" - ) - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - self.searchTask = task - self.collectionView.reloadData() - } - } + account: session.account, + handle: searchOperationHandle + ) if results.error != .success { await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) @@ -217,18 +204,9 @@ extension NCCollectionViewCommon { limit: 5, cursor: cursor, timeout: 60, - account: session.account - ) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier( - account: self.session.account, - name: "unifiedSearch" - ) - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - self.searchTask = task - self.collectionView.reloadData() - } - } + account: session.account, + handle: searchOperationHandle + ) if results.error != .success { await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) @@ -339,3 +317,4 @@ extension NCCollectionViewCommon { return metadata } } + diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index e639a046f3..8a20c4d6d6 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -49,6 +49,7 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, var syncMetadatasTask: Task? // Search var isSearchingMode: Bool = false + var searchOperationHandle = NKOperationHandle() var searchTask: URLSessionTask? var searchResultText: String? var searchResultStore: String? @@ -265,6 +266,24 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, DispatchQueue.main.async { self.collectionView?.collectionViewLayout.invalidateLayout() } + + Task { + for await event in await searchOperationHandle.events() { + switch event { + case .didSetTask(let task): + searchTask = task + if dataSource.isEmpty() { + collectionView.reloadData() + } + case .didSetRequest(let request): + print("Request available:", request) + case .didCancel: + print("Operation cancelled") + case .didClear: + print("Handle cleared") + } + } + } } override func viewWillAppear(_ animated: Bool) { @@ -315,13 +334,13 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - - self.searchTask?.cancel() dismissTip() // Cancel Queue & Retrieves Properties self.networking.downloadThumbnailQueue.cancelAll() - searchTask?.cancel() + Task { + await searchOperationHandle.cancel() + } } override func viewDidDisappear(_ animated: Bool) { @@ -516,13 +535,13 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, // mainNavigationController?.hiddenPlusButton(false) - self.searchTask?.cancel() self.isSearchingMode = false self.networkSearchInProgress = false self.searchResultText = nil self.searchResultStore = nil Task { + await searchOperationHandle.cancel() self.dataSource.removeAll() await self.reloadDataSource() } From 75e7e88c18c954b0eb5b1ecd7ed537eee7aa29c9 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 10 Feb 2026 15:56:56 +0100 Subject: [PATCH 2/5] code Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 2 +- iOSClient/Favorites/NCFavorite.swift | 2 +- iOSClient/Files/NCFiles.swift | 4 ++-- .../Collection Common/NCCollectionViewCommon+Search.swift | 2 +- .../NCCollectionViewCommon+SyncMetadata.swift | 2 +- iOSClient/Shares/NCShares.swift | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index b325871162..af8bc1a4b9 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -6002,7 +6002,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/marinofaggiana/LucidBanner"; requirement = { - branch = 0.5.0; + branch = main; kind = branch; }; }; diff --git a/iOSClient/Favorites/NCFavorite.swift b/iOSClient/Favorites/NCFavorite.swift index cb5bd4cfb6..4a020f53af 100644 --- a/iOSClient/Favorites/NCFavorite.swift +++ b/iOSClient/Favorites/NCFavorite.swift @@ -40,8 +40,8 @@ class NCFavorite: NCCollectionViewCommon { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - stopSyncMetadata() Task { + await stopSyncMetadata() await NCNetworking.shared.networkingTasks.cancel(identifier: "NCFavorite") } } diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index 36109db888..c712f876b6 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -43,8 +43,8 @@ class NCFiles: NCCollectionViewCommon { } NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in - self.stopSyncMetadata() Task { + await self.stopSyncMetadata() await self.searchOperationHandle.cancel() } } @@ -130,8 +130,8 @@ class NCFiles: NCCollectionViewCommon { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - stopSyncMetadata() Task { + await stopSyncMetadata() await NCNetworking.shared.networkingTasks.cancel(identifier: "\(self.serverUrl)_NCFiles") } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift index be2e7a2e20..dfecc15270 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift @@ -19,7 +19,7 @@ extension NCCollectionViewCommon { self.networkSearchInProgress = true // STOP PREEMPTIVE SYNC METADATA - self.stopSyncMetadata() + await self.stopSyncMetadata() // Clear datasotce self.dataSource.removeAll() self.collectionView.reloadData() diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SyncMetadata.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SyncMetadata.swift index 365f38d923..5e0254858d 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SyncMetadata.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SyncMetadata.swift @@ -42,7 +42,7 @@ extension NCCollectionViewCommon { /// Cancels the running sync task (if any) and releases the reference. /// /// Use this when the page/screen is about to disappear or the user navigates away. - func stopSyncMetadata() { + func stopSyncMetadata() async { if let task = syncMetadatasTask { if task.isCancelled { nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .stop, message: "Sync Metadata for \(self.serverUrl) was already cancelled.", consoleOnly: true) diff --git a/iOSClient/Shares/NCShares.swift b/iOSClient/Shares/NCShares.swift index c00af453eb..79674d943f 100644 --- a/iOSClient/Shares/NCShares.swift +++ b/iOSClient/Shares/NCShares.swift @@ -43,8 +43,8 @@ class NCShares: NCCollectionViewCommon { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - stopSyncMetadata() Task { + await stopSyncMetadata() await NCNetworking.shared.networkingTasks.cancel(identifier: "NCShares") backgroundTask?.cancel() } From a6d7ed996d1a41668e23e2779313513d27f9bd95 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 10 Feb 2026 16:11:37 +0100 Subject: [PATCH 3/5] code Signed-off-by: Marino Faggiana --- iOSClient/GUI/Lucid Banner/BannerView.swift | 32 +++++++++++++------ .../NCCollectionViewCommon+Search.swift | 20 +++++++----- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/iOSClient/GUI/Lucid Banner/BannerView.swift b/iOSClient/GUI/Lucid Banner/BannerView.swift index 005a702a8b..a6815a88ed 100644 --- a/iOSClient/GUI/Lucid Banner/BannerView.swift +++ b/iOSClient/GUI/Lucid Banner/BannerView.swift @@ -4,6 +4,7 @@ import SwiftUI import LucidBanner +import Alamofire // MARK: - Show Banner #if !EXTENSION @@ -133,7 +134,7 @@ func showInfoBanner(scene: UIWindowScene?, backgroundColor: UIColor = .systemBackground, errorCode: Int? = nil) async { #if !EXTENSION - guard !bannerContainsErrorCode(errorCode: errorCode) else { + guard !bannerContainsError(errorCode: errorCode) else { return } let scene = scene ?? UIApplication.shared.mainAppWindow?.windowScene @@ -176,7 +177,8 @@ func showErrorBannerActiveScenes(title: String = "_error_", foregroundColor: UIColor = .white, backgroundColor: UIColor = .red, sleepBefore: Double = 1, - errorCode: Int) async { + errorCode: Int, + afError: AFError? = nil) async { for scene in UIApplication.shared.foregroundActiveScenes { await showErrorBanner(scene: scene, title: title, @@ -185,7 +187,8 @@ func showErrorBannerActiveScenes(title: String = "_error_", foregroundColor: foregroundColor, backgroundColor: backgroundColor, sleepBefore: sleepBefore, - errorCode: errorCode) + errorCode: errorCode, + afError: afError) } } @@ -197,7 +200,8 @@ func showErrorBanner(controller: UITabBarController?, foregroundColor: UIColor = .white, backgroundColor: UIColor = .red, sleepBefore: Double = 1, - errorCode: Int) async { + errorCode: Int, + afError: AFError? = nil) async { let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene await showErrorBanner(scene: scene, text: text, @@ -205,7 +209,8 @@ func showErrorBanner(controller: UITabBarController?, foregroundColor: foregroundColor, backgroundColor: backgroundColor, sleepBefore: sleepBefore, - errorCode: errorCode) + errorCode: errorCode, + afError: afError) } @MainActor @@ -216,7 +221,8 @@ func showErrorBanner(sceneIdentifier: String?, foregroundColor: UIColor = .white, backgroundColor: UIColor = .red, sleepBefore: Double = 1, - errorCode: Int) async { + errorCode: Int, + afError: AFError? = nil) async { await showErrorBanner(controller: SceneManager.shared.getController(sceneIdentifier: sceneIdentifier), title: title, text: text, @@ -224,7 +230,8 @@ func showErrorBanner(sceneIdentifier: String?, foregroundColor: foregroundColor, backgroundColor: backgroundColor, sleepBefore: sleepBefore, - errorCode: errorCode) + errorCode: errorCode, + afError: afError) } #endif @@ -237,9 +244,10 @@ func showErrorBanner(scene: UIWindowScene?, foregroundColor: UIColor = .white, backgroundColor: UIColor = .red, sleepBefore: Double = 1, - errorCode: Int) async { + errorCode: Int, + afError: AFError? = nil) async { #if !EXTENSION - guard !bannerContainsErrorCode(errorCode: errorCode) else { + guard !bannerContainsError(errorCode: errorCode, afError: afError) else { return } let scene = scene ?? UIApplication.shared.mainAppWindow?.windowScene @@ -280,7 +288,7 @@ func showErrorBanner(scene: UIWindowScene?, // MARK: - Helper #if !EXTENSION -func bannerContainsErrorCode(errorCode: Int?) -> Bool { +func bannerContainsError(errorCode: Int?, afError: AFError? = nil) -> Bool { guard let errorCode else { return false } @@ -288,6 +296,9 @@ func bannerContainsErrorCode(errorCode: Int?) -> Bool { if errorCode == -999 { return true } + if let afError, case .explicitlyCancelled = afError { + return true + } // Prevent repeated display of the same user-facing error during the current foreground session. // If this error code has already been shown, do nothing. // Otherwise, record it and allow the UX notification to be displayed once. @@ -382,3 +393,4 @@ struct MessageBannerView: View { .padding() } } + diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift index dfecc15270..24ce38a348 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift @@ -120,12 +120,10 @@ extension NCCollectionViewCommon { } if results.error != .success { - // If Alamofire reported an explicit cancellation, skip showing an error banner - if let afError = results.error.error as? AFError, case .explicitlyCancelled = afError { - // Silently ignore cancellation - } else { - await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) - } + await showErrorBanner(controller: self.controller, + text: results.error.errorDescription, + errorCode: results.error.errorCode, + afError: results.error.error as? AFError) } guard isSearchingMode, @@ -155,7 +153,10 @@ extension NCCollectionViewCommon { ) if results.error != .success { - await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) + await showErrorBanner(controller: self.controller, + text: results.error.errorDescription, + errorCode: results.error.errorCode, + afError: results.error.error as? AFError) } guard isSearchingMode, @@ -209,7 +210,10 @@ extension NCCollectionViewCommon { ) if results.error != .success { - await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) + await showErrorBanner(controller: self.controller, + text: results.error.errorDescription, + errorCode: results.error.errorCode, + afError: results.error.error as? AFError) } guard isSearchingMode, From 21624f632ae3fa60581959cda3c44d56199f947f Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 10 Feb 2026 16:12:36 +0100 Subject: [PATCH 4/5] code Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index af8bc1a4b9..424e60882c 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -5759,7 +5759,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -5825,7 +5825,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; From 9b70f2e4ecb881ad74b27d52ea7e6f00d464c7a9 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 10 Feb 2026 16:15:12 +0100 Subject: [PATCH 5/5] code Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 424e60882c..c1fe168859 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -6122,7 +6122,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/nextcloud/NextcloudKit"; requirement = { - branch = datarequest; + branch = main; kind = branch; }; };