From f0517882e6b659295e2b1b501dbb15ee7e26679d Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 26 Feb 2026 16:40:30 +0100 Subject: [PATCH 01/20] clean Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 37 ++----------------------- iOSClient/Settings/Acknowledgements.rtf | 18 ------------ 2 files changed, 2 insertions(+), 53 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 4b3481b244..005281fb35 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -704,9 +704,6 @@ F7B769AE2B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B769A72B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift */; }; F7B82F182EBFA3B700F5F242 /* NCNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75A9EE523796C6F0044CFCE /* NCNetworking.swift */; }; F7B8B83025681C3400967775 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F7B8B82F25681C3400967775 /* GoogleService-Info.plist */; }; - F7B8F6142EAB64AD006A70D6 /* JDStatusBarNotification in Frameworks */ = {isa = PBXBuildFile; productRef = F7B8F6132EAB64AD006A70D6 /* JDStatusBarNotification */; }; - F7B8F6162EAB7503006A70D6 /* JDStatusBarNotification in Frameworks */ = {isa = PBXBuildFile; productRef = F7B8F6152EAB7503006A70D6 /* JDStatusBarNotification */; }; - F7B8F6182EAB7516006A70D6 /* JDStatusBarNotification in Frameworks */ = {isa = PBXBuildFile; productRef = F7B8F6172EAB7516006A70D6 /* JDStatusBarNotification */; }; F7B934FE2BDCFE1E002B2FC9 /* NCDragDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B934FD2BDCFE1E002B2FC9 /* NCDragDrop.swift */; }; F7BAADCB1ED5A87C00B7EAD4 /* NCManageDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BAADB51ED5A87C00B7EAD4 /* NCManageDatabase.swift */; }; F7BAADCC1ED5A87C00B7EAD4 /* NCManageDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BAADB51ED5A87C00B7EAD4 /* NCManageDatabase.swift */; }; @@ -1888,7 +1885,6 @@ F710FC80277B7D2700AA9FBF /* RealmSwift in Frameworks */, F74C863D2AEFBFD9009A1D4A /* LRUCache in Frameworks */, F70557B92ED44E4700135623 /* LucidBanner in Frameworks */, - F7B8F6182EAB7516006A70D6 /* JDStatusBarNotification in Frameworks */, F72AD70F28C24BA1006CB92D /* NextcloudKit in Frameworks */, F33EE6E72BF4C02600CA1A51 /* NIOSSL in Frameworks */, F77CB6A92AA08053000C3CA4 /* OpenSSL in Frameworks */, @@ -1909,7 +1905,6 @@ F7160A7D2BE931DE0034DCB3 /* RealmSwift in Frameworks */, F760DE052AE66EBE0027D78A /* KeychainAccess in Frameworks */, F7A560462AE15D3D00BE8FD6 /* Queuer in Frameworks */, - F7B8F6162EAB7503006A70D6 /* JDStatusBarNotification in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1933,7 +1928,6 @@ F788ECC7263AAAFA00ADC67F /* MarkdownKit in Frameworks */, F3374AF02D78B01B002A38F9 /* BitCollections in Frameworks */, F77BC3EB293E5268005F2B08 /* Swifter in Frameworks */, - F7B8F6142EAB64AD006A70D6 /* JDStatusBarNotification in Frameworks */, F7BB7E4727A18C56009B9F29 /* Parchment in Frameworks */, F33EE6E12BF4BDA500CA1A51 /* NIOSSL in Frameworks */, F734B06628E75C0100E180D5 /* TLPhotoPicker in Frameworks */, @@ -3529,7 +3523,6 @@ F760DE082AE66ED00027D78A /* KeychainAccess */, F74C863C2AEFBFD9009A1D4A /* LRUCache */, F33EE6E62BF4C02600CA1A51 /* NIOSSL */, - F7B8F6172EAB7516006A70D6 /* JDStatusBarNotification */, F70557B82ED44E4700135623 /* LucidBanner */, ); productName = "Share Ext"; @@ -3556,7 +3549,6 @@ F760DE042AE66EBE0027D78A /* KeychainAccess */, F7160A7C2BE931DE0034DCB3 /* RealmSwift */, F33EE6E22BF4C00700CA1A51 /* NIOSSL */, - F7B8F6152EAB7503006A70D6 /* JDStatusBarNotification */, ); productName = DashboardWidgetExtension; productReference = F7346E1028B0EF5B006CE2D2 /* Widget.appex */; @@ -3636,7 +3628,6 @@ F3374AF32D78B01B002A38F9 /* DequeModule */, F3374AF52D78B01B002A38F9 /* HashTreeCollections */, F3374AF72D78B01B002A38F9 /* HeapModule */, - F7B8F6132EAB64AD006A70D6 /* JDStatusBarNotification */, F70557B62ED44E2700135623 /* LucidBanner */, ); productName = "Crypto Cloud"; @@ -3816,7 +3807,6 @@ F33EE6EE2BF4C0FF00CA1A51 /* XCRemoteSwiftPackageReference "swift-nio" */, F7D4BF4E2CA2ECCB00A5E746 /* XCRemoteSwiftPackageReference "vlckit-spm" */, F3374AEE2D78B01B002A38F9 /* XCRemoteSwiftPackageReference "swift-collections" */, - F7B8F6122EAB64AD006A70D6 /* XCRemoteSwiftPackageReference "JDStatusBarNotification" */, F70557B52ED44E2700135623 /* XCRemoteSwiftPackageReference "LucidBanner" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; @@ -5729,7 +5719,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -5795,7 +5785,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -6096,14 +6086,6 @@ minimumVersion = 1.0.0; }; }; - F7B8F6122EAB64AD006A70D6 /* XCRemoteSwiftPackageReference "JDStatusBarNotification" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/calimarkus/JDStatusBarNotification"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.2.4; - }; - }; F7BB7E4527A18C56009B9F29 /* XCRemoteSwiftPackageReference "Parchment" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/rechsteiner/Parchment"; @@ -6522,21 +6504,6 @@ package = F710FC78277B7CFF00AA9FBF /* XCRemoteSwiftPackageReference "realm-swift" */; productName = RealmSwift; }; - F7B8F6132EAB64AD006A70D6 /* JDStatusBarNotification */ = { - isa = XCSwiftPackageProductDependency; - package = F7B8F6122EAB64AD006A70D6 /* XCRemoteSwiftPackageReference "JDStatusBarNotification" */; - productName = JDStatusBarNotification; - }; - F7B8F6152EAB7503006A70D6 /* JDStatusBarNotification */ = { - isa = XCSwiftPackageProductDependency; - package = F7B8F6122EAB64AD006A70D6 /* XCRemoteSwiftPackageReference "JDStatusBarNotification" */; - productName = JDStatusBarNotification; - }; - F7B8F6172EAB7516006A70D6 /* JDStatusBarNotification */ = { - isa = XCSwiftPackageProductDependency; - package = F7B8F6122EAB64AD006A70D6 /* XCRemoteSwiftPackageReference "JDStatusBarNotification" */; - productName = JDStatusBarNotification; - }; F7BB7E4627A18C56009B9F29 /* Parchment */ = { isa = XCSwiftPackageProductDependency; package = F7BB7E4527A18C56009B9F29 /* XCRemoteSwiftPackageReference "Parchment" */; diff --git a/iOSClient/Settings/Acknowledgements.rtf b/iOSClient/Settings/Acknowledgements.rtf index 2739fb8713..d2e89e9ec3 100644 --- a/iOSClient/Settings/Acknowledgements.rtf +++ b/iOSClient/Settings/Acknowledgements.rtf @@ -90,15 +90,6 @@ Copyright (c) VideoLAN\ __________________________________\ \ -\f1\b SVGKit -\f0\b0 \ -\ -https://github.com/SVGKit/SVGKit/blob/3.x/LICENSE\ -\ -Copyright (c) 2010-2011 Matt Rajca, 2011-2015 various authors (c) Tipbit Inc\ -__________________________________\ -\ - \f1\b SwiftRichString \f0\b0 \ \ @@ -178,15 +169,6 @@ Copyright (c) Tim Oliver\ __________________________________\ \ -\f1\b JDStatusBarNotification -\f0\b0 \ -\ -MIT License\ -\ -Copyright (c) M Emrich\ -__________________________________\ -\ - \f1\b EasyTipView \f0\b0 \ \ From aaa43f82640da80033e20a3222d9013c0777d0f6 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 26 Feb 2026 19:44:01 +0100 Subject: [PATCH 02/20] code Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 2 +- Share/NCShareExtension.swift | 45 ++-- iOSClient/Account/NCAccount.swift | 4 +- iOSClient/Activity/NCActivity.swift | 6 +- .../Activity/NCActivityTableViewCell.swift | 3 +- .../Assistant/Chat/NCAssistantChatModel.swift | 9 +- .../UIViewController+Extension.swift | 36 ++-- iOSClient/Files/NCFiles.swift | 14 +- .../Lucid Banner/AlertActionBannerView.swift | 19 +- iOSClient/GUI/Lucid Banner/BannerView.swift | 199 +++--------------- iOSClient/GUI/Lucid Banner/HelperBanner.swift | 2 +- .../GUI/Lucid Banner/HudBannerView.swift | 28 ++- .../GUI/Lucid Banner/UploadBannerView.swift | 19 +- iOSClient/Login/NCLogin.swift | 12 +- iOSClient/Login/NCLoginProvider.swift | 3 +- ...ionViewCommon+CollectionViewDelegate.swift | 23 +- .../NCCollectionViewCommon+Search.swift | 12 +- ...ctionViewCommon+SelectTabBarDelegate.swift | 24 ++- .../NCCollectionViewCommon.swift | 19 +- iOSClient/Main/Create/NCCreate.swift | 22 +- .../Create/NCCreateFormUploadConflict.swift | 8 +- iOSClient/Menu/ContextMenuActions.swift | 3 +- iOSClient/Menu/NCContextMenuComment.swift | 6 +- iOSClient/Menu/NCContextMenuMain.swift | 43 ++-- iOSClient/Menu/NCContextMenuPlus.swift | 6 +- iOSClient/Menu/NCContextMenuProfile.swift | 3 +- iOSClient/Menu/NCContextMenuShare.swift | 6 +- .../E2EE/NCNetworkingE2EEUpload.swift | 21 +- .../Networking/NCNetworking+ServerError.swift | 3 +- .../Networking/NCNetworking+Upload.swift | 3 +- .../Networking/NCNetworkingProcess.swift | 50 ++--- iOSClient/Networking/NCService.swift | 5 +- iOSClient/Notification/NCNotification.swift | 6 +- .../RichWorkspace/NCRichWorkspaceCommon.swift | 12 +- .../Scan document/NCUploadScanDocument.swift | 3 +- iOSClient/SceneDelegate.swift | 20 ++ .../Select/NCSelectOpen+SelectDelegate.swift | 6 +- .../AutoUpload/NCAutoUploadModel.swift | 3 +- .../Settings/E2EE/NCEndToEndInitialize.swift | 55 ++--- .../Settings/Settings/SetupPasscodeView.swift | 3 +- iOSClient/Share/NCShare+NCCellDelegate.swift | 3 +- iOSClient/Share/NCShareNetworking.swift | 21 +- .../NCTermOfServiceModel.swift | 3 +- iOSClient/Trash/NCTrash+Networking.swift | 6 +- iOSClient/UserStatus/NCUserStatusModel.swift | 9 +- iOSClient/Viewer/NCViewer.swift | 6 +- .../Viewer/NCViewerProviderContextMenu.swift | 6 +- .../NCViewerQuickLookView.swift | 3 +- .../NCViewerRichDocument.swift | 9 +- 49 files changed, 407 insertions(+), 425 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 005281fb35..5996ce3c68 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -5962,7 +5962,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/marinofaggiana/LucidBanner"; requirement = { - branch = main; + branch = variant; kind = branch; }; }; diff --git a/Share/NCShareExtension.swift b/Share/NCShareExtension.swift index b9979fc629..f874328041 100644 --- a/Share/NCShareExtension.swift +++ b/Share/NCShareExtension.swift @@ -49,8 +49,10 @@ class NCShareExtension: UIViewController { let global = NCGlobal.shared var maintenanceMode: Bool = false var token: Int? + var banner: LucidBanner? var sceneIdentifier: String = UUID().uuidString + // MARK: - View Life Cycle override func viewDidLoad() { @@ -114,6 +116,10 @@ class NCShareExtension: UIViewController { } NCNetworking.shared.setupScene(sceneIdentifier: sceneIdentifier, controller: self) + + if let windowScene = view.window?.windowScene { + banner = LucidBannerRegistry.shared.banner(for: windowScene) + } } override func viewWillAppear(_ animated: Bool) { @@ -383,10 +389,10 @@ extension NCShareExtension { vPosition: .center, horizontalLayout: horizontalLayout, blocksTouches: true) - token = showUploadBanner(scene: window.windowScene, - payload: payload, - allowMinimizeOnTap: false, - onButtonTap: { + (token, banner) = showUploadBanner(windowScene: window.windowScene, + payload: payload, + allowMinimizeOnTap: false, + onButtonTap: { self.cancel() }) @@ -397,7 +403,7 @@ extension NCShareExtension { systemImage: "arrowshape.up.circle", imageAnimation: .breathe, progress: 0) - LucidBanner.shared.update(payload: payloadUpdate) + banner?.update(payload: payloadUpdate) error = await self.upload(metadata: metadata) if error != .success { @@ -406,12 +412,12 @@ extension NCShareExtension { } if error == .success { - LucidBanner.shared.update(payload: LucidBannerPayload.Update(stage: .success, horizontalLayout: .centered(width: 100)), for: self.token) + banner?.update(payload: LucidBannerPayload.Update(stage: .success, horizontalLayout: .centered(width: 100)), for: self.token) } else { - LucidBanner.shared.update(payload: LucidBannerPayload.Update(subtitle: error?.errorDescription, stage: .error), for: self.token) + banner?.update(payload: LucidBannerPayload.Update(subtitle: error?.errorDescription, stage: .error), for: self.token) } - LucidBanner.shared.dismiss(after: 2) { + banner?.dismiss(after: 2) { self.cancel() } } @@ -444,15 +450,20 @@ extension NCShareExtension { self.counterUploaded += 1 if metadata.isDirectoryE2EE { - error = await NCNetworkingE2EEUpload().upload(metadata: metadata, session: session, controller: self, stageBanner: nil, tokenBanner: self.token) + error = await NCNetworkingE2EEUpload().upload(metadata: metadata, + session: session, + controller: self, + banner: banner, + stageBanner: nil, + tokenBanner: self.token) } else if metadata.chunk > 0 { - LucidBanner.shared.update(payload: LucidBannerPayload.Update(systemImage: "gearshape.arrow.triangle.2.circlepath", - imageAnimation: .rotate), + banner?.update(payload: LucidBannerPayload.Update(systemImage: "gearshape.arrow.triangle.2.circlepath", + imageAnimation: .rotate), for: self.token) let task = Task { () -> (account: String, file: NKFile?, error: NKError) in let results = await NCNetworking.shared.uploadChunkFile(metadata: metadata) { total, counter in Task {@MainActor in - LucidBanner.shared.update(payload: LucidBannerPayload.Update(progress: Double(counter) / Double(total)), for: self.token) + self.banner?.update(payload: LucidBannerPayload.Update(progress: Double(counter) / Double(total)), for: self.token) } } uploadStart: { _ in Task {@MainActor in @@ -460,11 +471,11 @@ extension NCShareExtension { systemImage: "arrowshape.up.circle", imageAnimation: .breathe ) - LucidBanner.shared.update(payload: payload, for: self.token) + self.banner?.update(payload: payload, for: self.token) } } uploadProgressHandler: { _, _, progress in Task {@MainActor in - LucidBanner.shared.update(payload: LucidBannerPayload.Update(progress: progress), for: self.token) + self.banner?.update(payload: LucidBannerPayload.Update(progress: progress), for: self.token) } } assembling: { Task {@MainActor in @@ -472,7 +483,7 @@ extension NCShareExtension { systemImage: "gearshape.arrow.triangle.2.circlepath", imageAnimation: .rotate ) - LucidBanner.shared.update(payload: payload, for: self.token) + self.banner?.update(payload: payload, for: self.token) } } @@ -494,8 +505,8 @@ extension NCShareExtension { dateModificationFile: metadata.date as Date) { _ in } progressHandler: { _, _, fractionCompleted in Task {@MainActor in - LucidBanner.shared.update(payload: LucidBannerPayload.Update(progress: fractionCompleted), - for: self.token) + self.banner?.update(payload: LucidBannerPayload.Update(progress: fractionCompleted), + for: self.token) } } error = results.error diff --git a/iOSClient/Account/NCAccount.swift b/iOSClient/Account/NCAccount.swift index e9d8d20190..ecf49a4adf 100644 --- a/iOSClient/Account/NCAccount.swift +++ b/iOSClient/Account/NCAccount.swift @@ -193,8 +193,8 @@ class NCAccount: NSObject { guard let tblAccount = await NCManageDatabase.shared.getTableAccountAsync(predicate: NSPredicate(format: "account == %@", account)) else { return } - - await showErrorBanner(controller: controller, text: String(format: NSLocalizedString("_account_unauthorized_", comment: ""), account), errorCode: NCGlobal.shared.errorUnauthorized401) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: String(format: NSLocalizedString("_account_unauthorized_", comment: ""), account), errorCode: NCGlobal.shared.errorUnauthorized401) let resultsWipe = await NextcloudKit.shared.getRemoteWipeStatusAsync(serverUrl: tblAccount.urlBase, token: token, account: account) { task in Task { diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index 2dc793228a..ec4657c765 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -83,7 +83,8 @@ class NCActivity: UIViewController, NCSharePagingContent { self.loadComments() } else { Task { - await showErrorBanner(controller: self.tabBarController, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: self.tabBarController) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } @@ -424,7 +425,8 @@ extension NCActivity { self.database.addComments(comments, account: metadata.account, objectId: metadata.fileId) } else if error.errorCode != NCGlobal.shared.errorResourceNotFound { Task { - await showErrorBanner(controller: self.tabBarController, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: self.tabBarController) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } diff --git a/iOSClient/Activity/NCActivityTableViewCell.swift b/iOSClient/Activity/NCActivityTableViewCell.swift index 35bd4932f0..89b55281dc 100644 --- a/iOSClient/Activity/NCActivityTableViewCell.swift +++ b/iOSClient/Activity/NCActivityTableViewCell.swift @@ -82,7 +82,8 @@ extension NCActivityTableViewCell: UICollectionViewDelegate { (responder as? UIViewController)!.navigationController?.pushViewController(viewController, animated: true) } else { Task { - await showErrorBanner(controller: viewController.controller, text: "_trash_file_not_found_", errorCode: 0) + let windowScene = SceneManager.shared.getWindowScene(controller: viewController.controller) + await showErrorBanner(windowScene: windowScene, text: "_trash_file_not_found_", errorCode: NCGlobal.shared.errorInternalError) } } } diff --git a/iOSClient/Assistant/Chat/NCAssistantChatModel.swift b/iOSClient/Assistant/Chat/NCAssistantChatModel.swift index 140f50870d..3cfb1d9a97 100644 --- a/iOSClient/Assistant/Chat/NCAssistantChatModel.swift +++ b/iOSClient/Assistant/Chat/NCAssistantChatModel.swift @@ -96,7 +96,8 @@ import NextcloudKit if result.error == .success { messages = result.chatMessages ?? [] } else { - await showErrorBanner(controller: controller, title: "_error_", text: "_assistant_error_load_messages_", errorCode: result.error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, title: "_error_", text: "_assistant_error_load_messages_", errorCode: result.error.errorCode) } } @@ -107,7 +108,8 @@ import NextcloudKit if result.error != .success { stopPolling() - await showErrorBanner(controller: controller, title: "_error_", text: "_assistant_error_generate_response_", errorCode: result.error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, title: "_error_", text: "_assistant_error_generate_response_", errorCode: result.error.errorCode) return } @@ -134,7 +136,8 @@ import NextcloudKit await generateChatSession() startPollingForResponse() } else { - await showErrorBanner(controller: controller, title: "_error_", text: "_assistant_error_send_message_", errorCode: 20) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, title: "_error_", text: "_assistant_error_send_message_", errorCode: 20) } isSending = false diff --git a/iOSClient/Extensions/UIViewController+Extension.swift b/iOSClient/Extensions/UIViewController+Extension.swift index 4f9cc274ba..4e27917d5c 100644 --- a/iOSClient/Extensions/UIViewController+Extension.swift +++ b/iOSClient/Extensions/UIViewController+Extension.swift @@ -1,32 +1,22 @@ -// -// UIViewController+Extension.swift -// Nextcloud -// -// Created by Marino Faggiana on 02/08/2022. -// Copyright © 2022 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: 2022-2026 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import Foundation import UIKit import MessageUI -import NextcloudKit +import LucidBanner extension UIViewController { + var lucidBanner: LucidBanner? { + guard let scene = view.window?.windowScene, + let delegate = scene.delegate as? SceneDelegate else { + return nil + } + + return delegate.lucidBanner + } + // https://stackoverflow.com/questions/6131205/how-to-find-topmost-view-controller-on-ios @objc func topMostViewController() -> UIViewController { // Handling Modal views diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index 6c44c327d3..11305e578c 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -230,6 +230,7 @@ class NCFiles: NCCollectionViewCommon { } private func networkReadFolderAsync(serverUrl: String, forced: Bool) async -> (metadatas: [tableMetadata]?, error: NKError, reloadRequired: Bool) { + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) var reloadRequired: Bool = false let resultsReadFile = await NCNetworking.shared.readFileAsync(serverUrlFileName: serverUrl, account: session.account) { task in Task { @@ -314,16 +315,15 @@ class NCFiles: NCCollectionViewCommon { guard results.error == .success, let e2eMetadata = results.e2eMetadata, let version = results.version else { - // No metadata fount, re-send it if results.error.errorCode == NCGlobal.shared.errorResourceNotFound { - await showInfoBanner(controller: self.controller, text: "Metadata not found") + await showInfoBanner(windowScene: windowScene, text: "Metadata not found") let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, account: account) if error != .success { - await showErrorBanner(controller: self.controller, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } else { - await showErrorBanner(controller: self.controller, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } return(metadatas, error, reloadRequired) @@ -335,20 +335,20 @@ class NCFiles: NCCollectionViewCommon { if errorDecodeMetadata == .success { let capabilities = await NKCapabilities.shared.getCapabilities(for: self.session.account) if version == "v1", NCGlobal.shared.isE2eeVersion2(capabilities.e2EEApiVersion) { - await showInfoBanner(controller: self.controller, text: "Conversion metadata v1 to v2 required, please wait...") + await showInfoBanner(windowScene: windowScene, text: "Conversion metadata v1 to v2 required, please wait...") nkLog(tag: self.global.logTagE2EE, message: "Conversion v1 to v2") NCActivityIndicator.shared.start() let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, updateVersionV1V2: true, account: account) if error != .success { - await showErrorBanner(controller: self.controller, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } NCActivityIndicator.shared.stop() } } else { // Client Diagnostic await self.database.addDiagnosticAsync(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors) - await showErrorBanner(controller: self.controller, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } return (metadatas, error, reloadRequired) diff --git a/iOSClient/GUI/Lucid Banner/AlertActionBannerView.swift b/iOSClient/GUI/Lucid Banner/AlertActionBannerView.swift index b0a733dcf9..7f36b4b842 100644 --- a/iOSClient/GUI/Lucid Banner/AlertActionBannerView.swift +++ b/iOSClient/GUI/Lucid Banner/AlertActionBannerView.swift @@ -6,11 +6,14 @@ import SwiftUI import LucidBanner @MainActor -func showAlertActionBannerView(scene: UIWindowScene?, +func showAlertActionBannerView(lucidBanner: LucidBanner?, title: String? = nil, subtitle: String? = nil, onConfirm: (() -> Void)? = nil) { - let isPad = scene?.traitCollection.userInterfaceIdiom == .pad + guard let lucidBanner else { + return + } + let isPad = lucidBanner.windowScene.traitCollection.userInterfaceIdiom == .pad let horizontalLayout: LucidBanner.HorizontalLayout = isPad ? .centered(width: 450) @@ -24,19 +27,19 @@ func showAlertActionBannerView(scene: UIWindowScene?, swipeToDismiss: true ) - LucidBanner.shared.show(scene: scene, - payload: payload, - policy: .replace) { _, _ in - LucidBanner.shared.dismiss() + + lucidBanner.show(payload: payload, + policy: .replace) { _, _ in + lucidBanner.dismiss() } content: { state in AlertActionBannerView( state: state, onConfirm: { onConfirm?() - LucidBanner.shared.dismiss() + lucidBanner.dismiss() }, onCancel: { - LucidBanner.shared.dismiss() + lucidBanner.dismiss() } ) } diff --git a/iOSClient/GUI/Lucid Banner/BannerView.swift b/iOSClient/GUI/Lucid Banner/BannerView.swift index 7ba259bf4a..f7fa9bc344 100644 --- a/iOSClient/GUI/Lucid Banner/BannerView.swift +++ b/iOSClient/GUI/Lucid Banner/BannerView.swift @@ -8,34 +8,8 @@ import NextcloudKit import Alamofire // MARK: - Show Banner -#if !EXTENSION -@MainActor -func showBannerActiveScenes(title: String?, - subtitle: String? = nil, - footnote: String? = nil, - textColor: UIColor, - image: String?, - imageAnimation: LucidBanner.LucidBannerAnimationStyle, - imageColor: UIColor, - vPosition: LucidBanner.VerticalPosition = .top, - backgroundColor: UIColor) async { - for scene in UIApplication.shared.foregroundActiveScenes { - await showBanner(scene: scene, - title: title, - subtitle: subtitle, - footnote: footnote, - textColor: textColor, - image: image, - imageAnimation: imageAnimation, - imageColor: imageColor, - vPosition: vPosition, - backgroundColor: backgroundColor) - } -} -#endif - @MainActor -func showBanner(scene: UIWindowScene?, +func showBanner(windowScene: UIWindowScene?, title: String?, subtitle: String? = nil, footnote: String? = nil, @@ -48,9 +22,10 @@ func showBanner(scene: UIWindowScene?, autoDismissAfter: TimeInterval = NCGlobal.shared.dismissAfterSecond, swipeToDismiss: Bool = true, policy: LucidBanner.ShowPolicy = .enqueue) async { -#if !EXTENSION - let scene = scene ?? UIApplication.shared.mainAppWindow?.windowScene -#endif + guard let windowScene else { + return + } + let payload = LucidBannerPayload( title: NSLocalizedString(title ?? "", comment: ""), subtitle: NSLocalizedString(subtitle ?? "", comment: ""), @@ -65,8 +40,9 @@ func showBanner(scene: UIWindowScene?, swipeToDismiss: swipeToDismiss ) - LucidBanner.shared.show( - scene: scene, + let banner = LucidBannerRegistry.shared.banner(for: windowScene) + + banner.show( payload: payload, policy: policy) { state in MessageBannerView(state: state) @@ -75,78 +51,29 @@ func showBanner(scene: UIWindowScene?, // MARK: - Show Info -#if !EXTENSION @MainActor -func showInfoBannerActiveScenes(title: String = "_error_", - text: String, - footnote: String? = nil, - foregroundColor: UIColor = .label, - backgroundColor: UIColor = .systemBackground, - errorCode: Int? = nil) async { - for scene in UIApplication.shared.foregroundActiveScenes { - await showInfoBanner(scene: scene, - title: title, - text: text, - footnote: footnote, - foregroundColor: foregroundColor, - backgroundColor: backgroundColor, - errorCode: errorCode) - } -} - -@MainActor -func showInfoBanner(controller: UITabBarController?, +func showInfoBanner(windowScene: UIWindowScene?, title: String = "_info_", text: String, footnote: String? = nil, foregroundColor: UIColor = .label, backgroundColor: UIColor = .systemBackground, errorCode: Int? = nil) async { - let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene - await showInfoBanner(scene: scene, - title: title, - text: text, - foregroundColor: foregroundColor, - backgroundColor: backgroundColor, - errorCode: errorCode) -} - -@MainActor -func showInfoBanner(sceneIdentifier: String?, - title: String = "_error_", - text: String, - footnote: String? = nil, - foregroundColor: UIColor = .label, - backgroundColor: UIColor = .systemBackground, - errorCode: Int? = nil) async { - await showInfoBanner(controller: SceneManager.shared.getController(sceneIdentifier: sceneIdentifier), - title: title, - text: text, - footnote: footnote, - foregroundColor: foregroundColor, - backgroundColor: backgroundColor, - errorCode: errorCode) -} - -#endif + guard let windowScene else { + return + } -@MainActor -func showInfoBanner(scene: UIWindowScene?, - title: String = "_info_", - text: String, - footnote: String? = nil, - foregroundColor: UIColor = .label, - backgroundColor: UIColor = .systemBackground, - errorCode: Int? = nil) async { #if !EXTENSION guard !bannerContainsError(errorCode: errorCode) else { return } - let scene = scene ?? UIApplication.shared.mainAppWindow?.windowScene #endif - guard let window = scene?.windows.first else { + + let banner = LucidBannerRegistry.shared.banner(for: windowScene) + guard let window = banner.windowScene.windows.first else { return } + let horizontalLayout = horizontalLayoutBanner(bounds: window.bounds, safeAreaInsets: window.safeAreaInsets, idiom: window.traitCollection.userInterfaceIdiom) @@ -165,71 +92,24 @@ func showInfoBanner(scene: UIWindowScene?, autoDismissAfter: NCGlobal.shared.dismissAfterSecond, swipeToDismiss: true, ) - LucidBanner.shared.show( - scene: scene, - payload: payload) { state in - MessageBannerView(state: state) - } + banner.show(payload: payload) { state in + MessageBannerView(state: state) + } } // MARK: - Show Error -#if !EXTENSION -@MainActor -func showErrorBannerActiveScenes(title: String = "_error_", - text: String, - footnote: String? = nil, - foregroundColor: UIColor = .white, - backgroundColor: UIColor = .red, - sleepBefore: Double = 1, - errorCode: Int, - afError: AFError? = nil) async { - for scene in UIApplication.shared.foregroundActiveScenes { - await showErrorBanner(scene: scene, - title: title, - text: text, - footnote: footnote, - foregroundColor: foregroundColor, - backgroundColor: backgroundColor, - sleepBefore: sleepBefore, - errorCode: errorCode, - afError: afError) - } -} - @MainActor -func showErrorBanner(controller: UITabBarController?, +func showErrorBanner(windowScene: UIWindowScene?, error: NKError) async { - let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene - await showErrorBanner(scene: scene, + await showErrorBanner(windowScene: windowScene, title: "_error_", text: error.errorDescription, errorCode: error.errorCode) } @MainActor -func showErrorBanner(controller: UITabBarController?, - title: String = "_error_", - text: String, - footnote: String? = nil, - foregroundColor: UIColor = .white, - backgroundColor: UIColor = .red, - sleepBefore: Double = 1, - errorCode: Int, - afError: AFError? = nil) async { - let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene - await showErrorBanner(scene: scene, - text: text, - footnote: footnote, - foregroundColor: foregroundColor, - backgroundColor: backgroundColor, - sleepBefore: sleepBefore, - errorCode: errorCode, - afError: afError) -} - -@MainActor -func showErrorBanner(sceneIdentifier: String?, +func showErrorBanner(windowScene: UIWindowScene?, title: String = "_error_", text: String, footnote: String? = nil, @@ -238,36 +118,18 @@ func showErrorBanner(sceneIdentifier: String?, sleepBefore: Double = 1, errorCode: Int, afError: AFError? = nil) async { - await showErrorBanner(controller: SceneManager.shared.getController(sceneIdentifier: sceneIdentifier), - title: title, - text: text, - footnote: footnote, - foregroundColor: foregroundColor, - backgroundColor: backgroundColor, - sleepBefore: sleepBefore, - errorCode: errorCode, - afError: afError) -} - -#endif + guard let windowScene else { + return + } -@MainActor -func showErrorBanner(scene: UIWindowScene?, - title: String = "_error_", - text: String, - footnote: String? = nil, - foregroundColor: UIColor = .white, - backgroundColor: UIColor = .red, - sleepBefore: Double = 1, - errorCode: Int, - afError: AFError? = nil) async { #if !EXTENSION guard !bannerContainsError(errorCode: errorCode, afError: afError) else { return } - let scene = scene ?? UIApplication.shared.mainAppWindow?.windowScene #endif - guard let window = scene?.windows.first else { + + let banner = LucidBannerRegistry.shared.banner(for: windowScene) + guard let window = banner.windowScene.windows.first else { return } let horizontalLayout = horizontalLayoutBanner(bounds: window.bounds, @@ -290,11 +152,10 @@ func showErrorBanner(scene: UIWindowScene?, autoDismissAfter: NCGlobal.shared.dismissAfterSecond, swipeToDismiss: true, ) - LucidBanner.shared.show( - scene: scene, + banner.show( payload: payload, onTap: { _, _ in - LucidBanner.shared.dismiss() + banner.dismiss() } ) { state in MessageBannerView(state: state) diff --git a/iOSClient/GUI/Lucid Banner/HelperBanner.swift b/iOSClient/GUI/Lucid Banner/HelperBanner.swift index 38bd2dddd3..878e6b8f32 100644 --- a/iOSClient/GUI/Lucid Banner/HelperBanner.swift +++ b/iOSClient/GUI/Lucid Banner/HelperBanner.swift @@ -20,7 +20,7 @@ public extension View { .contentShape(Rectangle()) .onTapGesture { guard allowMinimizeOnTap else { return } - LucidBannerVariantCoordinator.shared.handleTap(state) + //LucidBannerVariantCoordinator.shared.handleTap(state) } .frame(maxWidth: .infinity, alignment: .center) diff --git a/iOSClient/GUI/Lucid Banner/HudBannerView.swift b/iOSClient/GUI/Lucid Banner/HudBannerView.swift index ec839a5cdf..d44fa74895 100644 --- a/iOSClient/GUI/Lucid Banner/HudBannerView.swift +++ b/iOSClient/GUI/Lucid Banner/HudBannerView.swift @@ -6,14 +6,17 @@ import SwiftUI import LucidBanner @MainActor -func showHudBanner(scene: UIWindowScene?, +func showHudBanner(windowScene: UIWindowScene?, title: String? = nil, subtitle: String? = nil, stage: LucidBanner.Stage? = nil, - onButtonTap: (() -> Void)? = nil) -> Int? { - let scene = scene ?? UIApplication.shared.mainAppWindow?.windowScene + onButtonTap: (() -> Void)? = nil) -> (token: Int?, banner: LucidBanner?) { + guard let windowScene else { + return (nil, nil) + } let localizedTitle = title.map { NSLocalizedString($0, comment: "") } let localizedSubTitle = subtitle.map { NSLocalizedString($0, comment: "") } + let banner = LucidBannerRegistry.shared.banner(for: windowScene) let payload = LucidBannerPayload( title: localizedTitle, @@ -23,31 +26,38 @@ func showHudBanner(scene: UIWindowScene?, blocksTouches: true, ) - return LucidBanner.shared.show( - scene: scene, + let token = banner.show( payload: payload ) { state in HudBannerView(state: state, onButtonTap: onButtonTap) } + + return (token, banner) } @MainActor -func completeHudBannerSuccess(token: Int?) { +func completeHudBannerSuccess(token: Int?, banner: LucidBanner?) { + guard let banner else { + return + } let payload = LucidBannerPayload.Update( stage: .success, autoDismissAfter: 2 ) - LucidBanner.shared.update(payload: payload, for: token) + banner.update(payload: payload, for: token) } @MainActor -func completeHudBannerError(description: String, token: Int?) { +func completeHudBannerError(description: String, token: Int?, banner: LucidBanner?) { + guard let banner else { + return + } let payload = LucidBannerPayload.Update( subtitle: NSLocalizedString(description, comment: ""), stage: .error, autoDismissAfter: NCGlobal.shared.dismissAfterSecond ) - LucidBanner.shared.update(payload: payload, for: token) + banner.update(payload: payload, for: token) } // MARK: - SwiftUI diff --git a/iOSClient/GUI/Lucid Banner/UploadBannerView.swift b/iOSClient/GUI/Lucid Banner/UploadBannerView.swift index 0b6e1b4bb5..f01e529b32 100644 --- a/iOSClient/GUI/Lucid Banner/UploadBannerView.swift +++ b/iOSClient/GUI/Lucid Banner/UploadBannerView.swift @@ -6,13 +6,17 @@ import SwiftUI import LucidBanner @MainActor -func showUploadBanner(scene: UIWindowScene?, +func showUploadBanner(windowScene: UIWindowScene?, payload: LucidBannerPayload, allowMinimizeOnTap: Bool, - onButtonTap: (() -> Void)? = nil) -> Int? { - let token = LucidBanner.shared.show(scene: scene, - payload: payload, - policy: .drop) { state in + onButtonTap: (() -> Void)? = nil) -> (token: Int?, banner: LucidBanner?) { + guard let windowScene else { + return (nil, nil) + } + let banner = LucidBannerRegistry.shared.banner(for: windowScene) + + let token = banner.show(payload: payload, + policy: .drop) { state in UploadBannerView(state: state, allowMinimizeOnTap: allowMinimizeOnTap, onButtonTap: onButtonTap) @@ -20,7 +24,8 @@ func showUploadBanner(scene: UIWindowScene?, #if !EXTENSION if allowMinimizeOnTap { - LucidBannerVariantCoordinator.shared.register(token: token) { _ in + let coordinator = LucidBannerVariantCoordinator(banner: banner) + coordinator.register(token: token) { _ in return .init( payloadUpdate: .init( horizontalLayout: .centered(width: 100), @@ -31,7 +36,7 @@ func showUploadBanner(scene: UIWindowScene?, } } #endif - return token + return (token, banner) } // MARK: - SwiftUI diff --git a/iOSClient/Login/NCLogin.swift b/iOSClient/Login/NCLogin.swift index f9adc983a6..d4dcf1193d 100644 --- a/iOSClient/Login/NCLogin.swift +++ b/iOSClient/Login/NCLogin.swift @@ -192,18 +192,20 @@ class NCLogin: UIViewController, UITextFieldDelegate, NCLoginQRCodeDelegate { let title = String(format: NSLocalizedString("_apps_nextcloud_detect_", comment: ""), NCBrandOptions.shared.brand) let subtitle = String(format: NSLocalizedString("_add_existing_account_", comment: ""), NCBrandOptions.shared.brand) - showAlertActionBannerView(scene: view.window?.windowScene, + /* + showAlertActionBannerView(w: view.window?.windowScene, title: title, subtitle: subtitle) { self.openShareAccountsViewController(nil) } + */ } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - LucidBanner.shared.dismiss() + // LucidBanner.shared.dismiss() } private func handleLoginWithAppConfig() { @@ -424,7 +426,8 @@ class NCLogin: UIViewController, UITextFieldDelegate, NCLoginQRCodeDelegate { if results.error == .success, let token = results.token { await createAccount(urlBase: server, user: user, password: token) } else { - await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) dismiss(animated: true, completion: nil) } } else if value.hasPrefix(protocolLogin) { @@ -439,7 +442,8 @@ class NCLogin: UIViewController, UITextFieldDelegate, NCLoginQRCodeDelegate { if results.error == .success, let password = results.token { await self.createAccount(urlBase: urlBase, user: user, password: password) } else { - await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) dismiss(animated: true, completion: nil) } } diff --git a/iOSClient/Login/NCLoginProvider.swift b/iOSClient/Login/NCLoginProvider.swift index 8b6e495da3..d4e37877dd 100644 --- a/iOSClient/Login/NCLoginProvider.swift +++ b/iOSClient/Login/NCLoginProvider.swift @@ -52,7 +52,8 @@ class NCLoginProvider: NSObject, ASWebAuthenticationPresentationContextProviding func startAuthentication() { guard let url = URL(string: initialURLString) else { Task { - await showErrorBanner(controller: self.controller, text: "_login_url_error_", errorCode: 0) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: "_login_url_error_", errorCode: NCGlobal.shared.errorInternalError) } return } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift index 727b5cb405..a803452e84 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift @@ -12,6 +12,8 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { @MainActor func didSelectMetadata(_ metadata: tableMetadata, withOcIds: Bool) async { let capabilities = await NKCapabilities.shared.getCapabilities(for: session.account) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + if metadata.e2eEncrypted { if capabilities.e2EEEnabled { if !NCPreferences().isEndToEndEnabled(account: metadata.account) { @@ -21,20 +23,21 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { return } } else { - await showInfoBanner(controller: self.controller, text: "_e2e_server_disabled_") + await showInfoBanner(windowScene: windowScene, text: "_e2e_server_disabled_") return } } func downloadFile() async { var downloadRequest: DownloadRequest? - let scene = SceneManager.shared.getWindow(controller: self.tabBarController)?.windowScene + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + var banner : LucidBanner? var tokenBanner: Int? await MainActor.run { - tokenBanner = showHudBanner(scene: scene, - title: "_download_in_progress_", - stage: .button, - onButtonTap: { + (tokenBanner, banner) = showHudBanner(windowScene: windowScene, + title: "_download_in_progress_", + stage: .button, + onButtonTap: { if let request = downloadRequest { request.cancel() } @@ -52,19 +55,19 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { downloadRequest = request } progressHandler: { progress in Task {@MainActor in - LucidBanner.shared.update( + banner?.update( payload: LucidBannerPayload.Update(progress: Double(progress.fractionCompleted)), for: tokenBanner) } } await MainActor.run { - LucidBanner.shared.dismiss() + banner?.dismiss() } if results.nkError == .success || results.afError?.isExplicitlyCancelledError ?? false { print("ok") } else { - await showErrorBanner(scene: scene, text: results.nkError.errorDescription, errorCode: results.nkError.errorCode) + await showErrorBanner(windowScene: windowScene, text: results.nkError.errorDescription, errorCode: results.nkError.errorCode) } } @@ -115,7 +118,7 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { self.navigationController?.pushViewController(vc, animated: true) } } else { - await showErrorBanner(controller: controller, text: "_go_online_", errorCode: NCGlobal.shared.errorOfflineNotAllowed) + await showErrorBanner(windowScene: windowScene, text: "_go_online_", errorCode: NCGlobal.shared.errorOfflineNotAllowed) } } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift index 208248b8b8..487757287d 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift @@ -75,7 +75,8 @@ extension NCCollectionViewCommon { account: self.session.account ) } else { - await showErrorBanner(controller: self.controller, + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) } @@ -117,7 +118,8 @@ extension NCCollectionViewCommon { } if results.error != .success { - await showErrorBanner(controller: self.controller, + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode, afError: results.error.error as? AFError) @@ -150,7 +152,8 @@ extension NCCollectionViewCommon { ) if results.error != .success { - await showErrorBanner(controller: self.controller, + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode, afError: results.error.error as? AFError) @@ -209,7 +212,8 @@ extension NCCollectionViewCommon { ) if results.error != .success { - await showErrorBanner(controller: self.controller, + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode, afError: results.error.error as? AFError) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift index e788ce1b7b..103213aa05 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift @@ -43,13 +43,14 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { if !metadatasPlain.isEmpty { let error = await self.networking.setStatusWaitDelete(metadatas: metadatasPlain) if error != .success { - await showErrorBanner(controller: self.controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: error) } } if !metadatasE2EE.isEmpty { if self.networking.isOffline { - await showErrorBanner(controller: self.controller, + await showErrorBanner(windowScene: self.windowScene, text: "_offline_not_allowed_", errorCode: self.global.errorOfflineNotAllowed) } else { @@ -57,8 +58,8 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { var num: Float = 0 let total = Float(metadatasE2EE.count) - let token = showHudBanner( - scene: self.scene, + let bannerResults = showHudBanner( + windowScene: self.windowScene, title: "_delete_in_progress_", stage: .button) { cancelOnTap = true @@ -66,15 +67,15 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { for metadata in metadatasE2EE { let error = await NCNetworkingE2EEDelete().delete(metadata: metadata) num += 1 - LucidBanner.shared.update( + bannerResults.banner?.update( payload: LucidBannerPayload.Update(progress: Double(num) / Double(total)), - for: token + for: bannerResults.token ) if cancelOnTap || error != .success { break } } - LucidBanner.shared.dismiss() + bannerResults.banner?.dismiss() } } await self.reloadDataSource() @@ -85,16 +86,17 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { alertController.addAction(UIAlertAction(title: NSLocalizedString("_remove_local_file_", comment: ""), style: .default) { (_: UIAlertAction) in Task { var token: Int? + var banner: LucidBanner? let containsDirectory = metadatas.contains { $0.isDirectory } if containsDirectory { - token = showHudBanner(scene: self.scene, title: "_delete_in_progress_") + (token, banner) = showHudBanner(windowScene: self.windowScene, title: "_delete_in_progress_") } for metadata in metadatas { await self.networking.deleteCache(metadata, progress: { progress in Task { if let token { - LucidBanner.shared.update( + banner?.update( payload: LucidBannerPayload.Update(progress: progress), for: token ) @@ -102,7 +104,7 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { } }) - LucidBanner.shared.dismiss() + banner?.dismiss() } await self.setEditMode(false) } @@ -167,7 +169,7 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { for metadata in metadatas where metadata.lock == isAnyLocked { let error = await self.networking.lockUnlockFile(metadata, shouldLock: !isAnyLocked) if error != .success { - await showErrorBanner(controller: self.controller, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } await setEditMode(false) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index b34e9f899a..130b572d11 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -137,8 +137,8 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, (self.tabBarController as? NCMainTabBarController)?.sceneIdentifier ?? "" } - internal var scene: UIWindowScene? { - SceneManager.shared.getWindow(sceneIdentifier: self.controller?.sceneIdentifier)?.windowScene + internal var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: self.tabBarController as? NCMainTabBarController) } internal var isNumberOfItemsInAllSectionsNull: Bool { @@ -627,9 +627,8 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, guard let tblAccount = await NCManageDatabase.shared.getTableAccountAsync(account: session.account) else { return } - let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene - let token = showHudBanner( - scene: scene, + let bannerResults = showHudBanner( + windowScene: windowScene, title: "_upload_in_progress_") for (index, items) in UIPasteboard.general.items.enumerated() { @@ -661,9 +660,9 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, serverUrlFileName: serverUrlFileName) { _ in } progressHandler: { _, _, fractionCompleted in Task {@MainActor in - LucidBanner.shared.update( + bannerResults.banner?.update( payload: LucidBannerPayload.Update(progress: fractionCompleted), - for: token + for: bannerResults.token ) } } @@ -689,12 +688,12 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, } } else { Task { - await showErrorBanner(scene: scene, text: resultsUpload.error.errorDescription, errorCode: resultsUpload.error.errorCode) + await showErrorBanner(windowScene: windowScene, text: resultsUpload.error.errorDescription, errorCode: resultsUpload.error.errorCode) } } } } - LucidBanner.shared.dismiss() + bannerResults.banner?.dismiss() } } @@ -878,7 +877,7 @@ extension NCCollectionViewCommon: NCTransferDelegate { Task { if error != .success, error.errorCode != global.errorResourceNotFound { - await showErrorBanner(controller: self.controller, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } guard session.account == account else { return diff --git a/iOSClient/Main/Create/NCCreate.swift b/iOSClient/Main/Create/NCCreate.swift index 91c1429390..02f055a7b2 100644 --- a/iOSClient/Main/Create/NCCreate.swift +++ b/iOSClient/Main/Create/NCCreate.swift @@ -41,7 +41,8 @@ class NCCreate: NSObject { } guard results.error == .success, let url = results.url else { Task { - await showErrorBanner(controller: controller, text: results.error.errorDescription, errorCode: results.error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) } return } @@ -68,7 +69,8 @@ class NCCreate: NSObject { } guard results.error == .success, let url = results.url else { Task { - await showErrorBanner(controller: controller, text: results.error.errorDescription, errorCode: results.error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) } return } @@ -250,7 +252,7 @@ class NCCreate: NSObject { let metadatas = selectedMetadata.filter { !$0.directory } var exportURLs: [URL] = [] var downloadMetadata: [(tableMetadata, URL)] = [] - let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene + let windowScene = SceneManager.shared.getWindowScene(controller: controller) var downloadRequest: DownloadRequest? for metadata in metadatas { @@ -270,9 +272,9 @@ class NCCreate: NSObject { } if !downloadMetadata.isEmpty { - let token = showHudBanner(scene: scene, - title: "_download_in_progress_", - stage: .button) { + let bannerResults = showHudBanner(windowScene: windowScene, + title: "_download_in_progress_", + stage: .button) { if let downloadRequest { downloadRequest.cancel() } @@ -285,7 +287,7 @@ class NCCreate: NSObject { selector: "", sceneIdentifier: controller.sceneIdentifier ) else { - LucidBanner.shared.dismiss() + bannerResults.banner?.dismiss() return } @@ -295,9 +297,9 @@ class NCCreate: NSObject { downloadRequest = request } progressHandler: { progress in Task { @MainActor in - LucidBanner.shared.update( + bannerResults.banner?.update( payload: LucidBannerPayload.Update(progress: progress.fractionCompleted), - for: token) + for: bannerResults.token) } } @@ -308,7 +310,7 @@ class NCCreate: NSObject { } } - LucidBanner.shared.dismiss() + bannerResults.banner?.dismiss() } guard !exportURLs.isEmpty else { return } diff --git a/iOSClient/Main/Create/NCCreateFormUploadConflict.swift b/iOSClient/Main/Create/NCCreateFormUploadConflict.swift index 3903b907b4..b1937b3299 100644 --- a/iOSClient/Main/Create/NCCreateFormUploadConflict.swift +++ b/iOSClient/Main/Create/NCCreateFormUploadConflict.swift @@ -226,11 +226,11 @@ class NCCreateFormUploadConflict: UIViewController { Task { #if EXTENSION - let scene = self.view.window?.windowScene - await showErrorBanner(scene: scene, text: "_file_not_rewite_doc_", errorCode: NCGlobal.shared.errorInternalError) + let windowScene = self.view.window?.windowScene + await showErrorBanner(windowScene: windowScene, text: "_file_not_rewite_doc_", errorCode: NCGlobal.shared.errorInternalError) #else - let controller = self.tabBarController as? NCMainTabBarController - await showErrorBanner(controller: controller, text: "_file_not_rewite_doc_", errorCode: NCGlobal.shared.errorInternalError) + let windowScene = SceneManager.shared.getWindowScene(controller: self.tabBarController as? NCMainTabBarController) + await showErrorBanner(windowScene: windowScene, text: "_file_not_rewite_doc_", errorCode: NCGlobal.shared.errorInternalError) #endif } diff --git a/iOSClient/Menu/ContextMenuActions.swift b/iOSClient/Menu/ContextMenuActions.swift index 46acf61553..8e56a1e7b4 100644 --- a/iOSClient/Menu/ContextMenuActions.swift +++ b/iOSClient/Menu/ContextMenuActions.swift @@ -145,7 +145,8 @@ enum ContextMenuActions { Task { let error = await NCNetworking.shared.lockUnlockFile(metadata, shouldLock: !isLocked) if error != .success { - await showErrorBanner(controller: controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, error: error) } completion?() } diff --git a/iOSClient/Menu/NCContextMenuComment.swift b/iOSClient/Menu/NCContextMenuComment.swift index e1c300ed9f..37a32cdebd 100644 --- a/iOSClient/Menu/NCContextMenuComment.swift +++ b/iOSClient/Menu/NCContextMenuComment.swift @@ -62,7 +62,8 @@ class NCContextMenuComment: NSObject { NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) } else { Task { @MainActor in - await showErrorBanner(controller: self.viewController?.tabBarController, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } @@ -96,7 +97,8 @@ class NCContextMenuComment: NSObject { (self.viewController as? NCActivity)?.loadComments() } else { Task { @MainActor in - await showErrorBanner(controller: self.controller, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } diff --git a/iOSClient/Menu/NCContextMenuMain.swift b/iOSClient/Menu/NCContextMenuMain.swift index 16915b78a9..80926cc0e4 100644 --- a/iOSClient/Menu/NCContextMenuMain.swift +++ b/iOSClient/Menu/NCContextMenuMain.swift @@ -25,7 +25,7 @@ class NCContextMenuMain: NSObject { controller?.sceneIdentifier ?? "" } - internal var scene: UIWindowScene? { + internal var windowScene: UIWindowScene? { SceneManager.shared.getWindow(sceneIdentifier: self.controller?.sceneIdentifier)?.windowScene } @@ -243,7 +243,8 @@ class NCContextMenuMain: NSObject { userId: metadata.userId ) if error != .success { - await showErrorBanner(controller: self.controller, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } @@ -281,7 +282,8 @@ class NCContextMenuMain: NSObject { await NCManageDatabase.shared.setMetadataEncryptedAsync(ocId: metadata.ocId, encrypted: false) await (self.viewController as? NCCollectionViewCommon)?.reloadDataSource() } else { - await showErrorBanner(controller: self.controller, + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, title: "_e2e_error_", text: results.error.errorDescription, errorCode: results.error.errorCode) @@ -357,7 +359,8 @@ class NCContextMenuMain: NSObject { fileNameNew ) ) != nil { - await showErrorBanner(controller: self.controller, + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: "_rename_already_exists_", errorCode: 0) return @@ -365,7 +368,8 @@ class NCContextMenuMain: NSObject { let error = await NCNetworking.shared.setStatusWaitRename(metadata, fileNameNew: fileNameNew) if error != .success { - await showErrorBanner(controller: self.controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: error) } } } @@ -464,19 +468,20 @@ class NCContextMenuMain: NSObject { Task { if metadata.isDirectoryE2EE { if NCNetworking.shared.isOffline { - await showErrorBanner(controller: self.controller, + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: "_offline_not_allowed_", errorCode: NCGlobal.shared.errorOfflineNotAllowed) } else { - let token = await showHudBanner(scene: self.scene, - title: "_delete_in_progress_") + let results = await showHudBanner(windowScene: self.windowScene, + title: "_delete_in_progress_") let error = await NCNetworkingE2EEDelete().delete(metadata: metadata) if error == .success { - await completeHudBannerSuccess(token: token) + await completeHudBannerSuccess(token: results.token, banner: results.banner) } else { - await completeHudBannerError(description: error.errorDescription, token: token) + await completeHudBannerError(description: error.errorDescription, token: results.token, banner: results.banner) } await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in @@ -486,7 +491,8 @@ class NCContextMenuMain: NSObject { } else { let error = await NCNetworking.shared.setStatusWaitDelete(metadatas: [metadata]) if error != .success { - await showErrorBanner(controller: self.controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: error) } } } @@ -505,9 +511,11 @@ class NCContextMenuMain: NSObject { ) { _ in Task { @MainActor in var token: Int? + var banner: LucidBanner? if metadata.isDirectory { - token = showHudBanner( - scene: self.scene, + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + (token, banner) = showHudBanner( + windowScene: windowScene, title: "_delete_in_progress_" ) } @@ -515,7 +523,7 @@ class NCContextMenuMain: NSObject { await NCNetworking.shared.deleteCache(metadata, progress: { progress in Task { if let token { - LucidBanner.shared.update( + banner?.update( payload: LucidBannerPayload.Update(progress: progress), for: token ) @@ -523,7 +531,7 @@ class NCContextMenuMain: NSObject { } }) - LucidBanner.shared.dismiss() + banner?.dismiss() } } } @@ -575,6 +583,7 @@ class NCContextMenuMain: NSObject { image: iconImage ) { _ in Task { + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) let results = await NextcloudKit.shared.sendRequestAsync( account: metadata.account, fileId: metadata.fileId, @@ -584,12 +593,12 @@ class NCContextMenuMain: NSObject { params: item.params ) if results.error != .success { - await showErrorBanner(controller: self.controller, + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) } else { if let tooltip = results.uiResponse?.ocs.data.tooltip { - await showInfoBanner(controller: self.controller, text: tooltip) + await showInfoBanner(windowScene: windowScene, text: tooltip) } else { let baseURL = metadata.urlBase diff --git a/iOSClient/Menu/NCContextMenuPlus.swift b/iOSClient/Menu/NCContextMenuPlus.swift index 05ebf8d9d8..9b6ae683e6 100644 --- a/iOSClient/Menu/NCContextMenuPlus.swift +++ b/iOSClient/Menu/NCContextMenuPlus.swift @@ -90,7 +90,8 @@ class NCContextMenuPlus: NSObject { capabilities: capabilities) { error in if error != .success { Task { - await showErrorBanner(controller: controller, + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } @@ -116,7 +117,8 @@ class NCContextMenuPlus: NSObject { capabilities: capabilities) { error in if error != .success { Task { - await showErrorBanner(controller: controller, + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } diff --git a/iOSClient/Menu/NCContextMenuProfile.swift b/iOSClient/Menu/NCContextMenuProfile.swift index 2857b1ee10..3b31845956 100644 --- a/iOSClient/Menu/NCContextMenuProfile.swift +++ b/iOSClient/Menu/NCContextMenuProfile.swift @@ -180,7 +180,8 @@ class NCContextMenuProfile: NSObject { private func showError(_ errorKey: String) { Task { - await showErrorBanner(controller: controller, + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: errorKey, errorCode: NCGlobal.shared.errorInternalError) } diff --git a/iOSClient/Menu/NCContextMenuShare.swift b/iOSClient/Menu/NCContextMenuShare.swift index dc4389e848..1c1ee434e0 100644 --- a/iOSClient/Menu/NCContextMenuShare.swift +++ b/iOSClient/Menu/NCContextMenuShare.swift @@ -155,7 +155,8 @@ class NCContextMenuShare: NSObject { metadata.e2eEncrypted && NCGlobal.shared.isE2eeVersion2(capabilities.e2EEApiVersion) { if await NCNetworkingE2EE().isInUpload(account: metadata.account, serverUrl: metadata.serverUrlFileName) { Task { - await showErrorBanner(controller: controller, + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: "_e2e_in_upload_", errorCode: NCGlobal.shared.errorE2EEUploadInProgress) } @@ -164,7 +165,8 @@ class NCContextMenuShare: NSObject { let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: metadata.serverUrlFileName, addUserId: nil, removeUserId: share.shareWith, account: metadata.account) if error != .success { Task { - await showErrorBanner(controller: controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, error: error) } return } diff --git a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift index 85e181c1a7..81fd27bf5c 100644 --- a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift +++ b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift @@ -22,6 +22,7 @@ class NCNetworkingE2EEUpload: NSObject { func upload(metadata: tableMetadata, session: NCSession.Session? = nil, controller: UIViewController? = nil, + banner: LucidBanner?, stageBanner: LucidBanner.Stage?, tokenBanner: Int?, requestHandle: @escaping (_ request: UploadRequest) -> Void = { _ in }, @@ -52,8 +53,8 @@ class NCNetworkingE2EEUpload: NSObject { payload.subtitle = NSLocalizedString("_e2ee_upload_tip_", comment: "") payload.systemImage = "lock.circle.fill" - LucidBanner.shared.update(payload: payload, for: tokenBanner) - LucidBanner.shared.requestRelayout(animated: true) + banner?.update(payload: payload, for: tokenBanner) + banner?.requestRelayout(animated: true) if let result = await self.database.getMetadataAsync(predicate: NSPredicate(format: "serverUrl == %@ AND fileNameView == %@ AND ocId != %@", metadata.serverUrl, metadata.fileNameView, metadata.ocId)) { metadata.fileName = result.fileName @@ -163,6 +164,7 @@ class NCNetworkingE2EEUpload: NSObject { let resultsSendFile = await sendFile(metadata: metadata, e2eToken: e2eToken, controller: controller, + banner: banner, stageBanner: stageBanner, tokenBanner: tokenBanner) { request in requestHandle(request) @@ -218,6 +220,7 @@ class NCNetworkingE2EEUpload: NSObject { private func sendFile(metadata: tableMetadata, e2eToken: String, controller: UIViewController?, + banner: LucidBanner?, stageBanner: LucidBanner.Stage?, tokenBanner: Int?, requestHandle: @escaping (_ request: UploadRequest) -> Void = { _ in }, @@ -231,13 +234,13 @@ class NCNetworkingE2EEUpload: NSObject { progress: 0, stage: stageBanner ) - LucidBanner.shared.update(payload: payload, for: tokenBanner) + banner?.update(payload: payload, for: tokenBanner) let task = Task { () -> (account: String, file: NKFile?, error: NKError) in let results = await NCNetworking.shared.uploadChunkFile(metadata: metadata) { total, counter in Task {@MainActor in let progress = Double(counter) / Double(total) - LucidBanner.shared.update(payload: LucidBannerPayload.Update(progress: progress), for: tokenBanner) + banner?.update(payload: LucidBannerPayload.Update(progress: progress), for: tokenBanner) } } uploadStart: { _ in Task {@MainActor in @@ -247,11 +250,11 @@ class NCNetworkingE2EEUpload: NSObject { imageAnimation: .breathe, progress: 0 ) - LucidBanner.shared.update(payload: payload, for: tokenBanner) + banner?.update(payload: payload, for: tokenBanner) } } uploadProgressHandler: { _, _, progress in Task {@MainActor in - LucidBanner.shared.update( + banner?.update( payload: LucidBannerPayload.Update(progress: progress), for: tokenBanner) } @@ -264,7 +267,7 @@ class NCNetworkingE2EEUpload: NSObject { progress: 0, stage: .placeholder ) - LucidBanner.shared.update(payload: payload, for: tokenBanner) + banner?.update(payload: payload, for: tokenBanner) } } @@ -282,7 +285,7 @@ class NCNetworkingE2EEUpload: NSObject { progress: 0, stage: stageBanner ) - LucidBanner.shared.update(payload: payload, for: tokenBanner) + banner?.update(payload: payload, for: tokenBanner) let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileName: metadata.fileName, @@ -298,7 +301,7 @@ class NCNetworkingE2EEUpload: NSObject { requestHandle(request) } progressHandler: { _, _, fractionCompleted in Task {@MainActor in - LucidBanner.shared.update( + banner?.update( payload: LucidBannerPayload.Update(progress: fractionCompleted), for: tokenBanner) } diff --git a/iOSClient/Networking/NCNetworking+ServerError.swift b/iOSClient/Networking/NCNetworking+ServerError.swift index 43d9d0cac4..5f24effce0 100644 --- a/iOSClient/Networking/NCNetworking+ServerError.swift +++ b/iOSClient/Networking/NCNetworking+ServerError.swift @@ -71,7 +71,8 @@ extension NCNetworking { if serverInfo.maintenance { Task { - await showInfoBanner(controller: controller, + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showInfoBanner(windowScene: windowScene, title: "_warning_", text: "_maintenance_mode_", errorCode: 401) diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 93929f64c1..2979830d08 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -292,7 +292,8 @@ extension NCNetworking { } else if (error.errorCode == self.global.errorBadRequest || error.errorCode == self.global.errorUnsupportedMediaType) && error.errorDescription.localizedCaseInsensitiveContains("virus") { await uploadCancelFile(metadata: metadata) #if !EXTENSION - await showErrorBanner(sceneIdentifier: metadata.sceneIdentifier, text: "_virus_detect_", errorCode: self.global.errorBadRequest) + let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: metadata.sceneIdentifier) + await showErrorBanner(windowScene: windowScene, text: "_virus_detect_", errorCode: self.global.errorBadRequest) #endif // Client Diagnostic await NCManageDatabase.shared.addDiagnosticAsync(account: metadata.account, issue: self.global.diagnosticIssueVirusDetected) diff --git a/iOSClient/Networking/NCNetworkingProcess.swift b/iOSClient/Networking/NCNetworkingProcess.swift index cc62842c80..97512a86e9 100644 --- a/iOSClient/Networking/NCNetworkingProcess.swift +++ b/iOSClient/Networking/NCNetworkingProcess.swift @@ -412,8 +412,8 @@ actor NCNetworkingProcess { // UPLOAD E2EE // if metadata.isDirectoryE2EE, - let scene = await SceneManager.shared.getWindow(sceneIdentifier: metadata.sceneIdentifier)?.windowScene, - let window = await scene.windows.first { + let windowScene = await SceneManager.shared.getWindow(sceneIdentifier: metadata.sceneIdentifier)?.windowScene, + let window = await windowScene.windows.first { let controller = await getController(account: metadata.account, sceneIdentifier: metadata.sceneIdentifier) let horizontalLayout = await horizontalLayoutBanner(bounds: window.bounds, safeAreaInsets: window.safeAreaInsets, @@ -422,7 +422,7 @@ actor NCNetworkingProcess { horizontalLayout: horizontalLayout, blocksTouches: true, draggable: false) - let token = await showUploadBanner(scene: scene, + let bannerResults = await showUploadBanner(windowScene: windowScene, payload: payload, allowMinimizeOnTap: false, onButtonTap: { @@ -433,8 +433,9 @@ actor NCNetworkingProcess { await NCNetworkingE2EEUpload().upload(metadata: metadata, controller: controller, + banner: bannerResults.banner, stageBanner: .button, - tokenBanner: token) { uploadRequest in + tokenBanner: bannerResults.token) { uploadRequest in Task {@MainActor in self.currentUploadRequest = uploadRequest } @@ -445,7 +446,7 @@ actor NCNetworkingProcess { } // wait dismiss banner before open another (loop) - await LucidBanner.shared.dismissAsync() + await bannerResults.banner?.dismissAsync() // UPLOAD CHUNK // @@ -466,32 +467,33 @@ actor NCNetworkingProcess { @MainActor func uploadChunk(metadata: tableMetadata) async { - guard let scene = SceneManager.shared.getWindow(sceneIdentifier: metadata.sceneIdentifier)?.windowScene, - let window = scene.windows.first else { + guard let windowScene = SceneManager.shared.getWindow(sceneIdentifier: metadata.sceneIdentifier)?.windowScene, + let window = windowScene.windows.first else { return } var tokenBanner: Int? + var banner: LucidBanner? let horizontalLayout = horizontalLayoutBanner(bounds: window.bounds, safeAreaInsets: window.safeAreaInsets, idiom: window.traitCollection.userInterfaceIdiom) - tokenBanner = showUploadBanner(scene: scene, - payload: LucidBannerPayload(stage: .button, - backgroundColor: Color(.systemBackground), - vPosition: .bottom, - verticalMargin: 50, - horizontalLayout: horizontalLayout, - blocksTouches: false, - draggable: true), - allowMinimizeOnTap: true, - onButtonTap: { + (tokenBanner, banner) = showUploadBanner(windowScene: windowScene, + payload: LucidBannerPayload(stage: .button, + backgroundColor: Color(.systemBackground), + vPosition: .bottom, + verticalMargin: 50, + horizontalLayout: horizontalLayout, + blocksTouches: false, + draggable: true), + allowMinimizeOnTap: true, + onButtonTap: { Task { await self.cancelCurrentUpload() - LucidBanner.shared.dismiss() + banner?.dismiss() } }) - LucidBanner.shared.update(payload: LucidBannerPayload.Update( + banner?.update(payload: LucidBannerPayload.Update( title: NSLocalizedString("_wait_file_preparation_", comment: ""), subtitle: NSLocalizedString("_large_upload_tip_", comment: ""), footnote: "( " + NSLocalizedString("_tap_to_min_max_", comment: "") + " )", @@ -502,14 +504,14 @@ actor NCNetworkingProcess { let task = Task { () -> (account: String, file: NKFile?, error: NKError) in let results = await NCNetworking.shared.uploadChunkFile(metadata: metadata) { total, counter in Task { - LucidBanner.shared.update( + banner?.update( payload: LucidBannerPayload.Update(progress: Double(counter) / Double(total)), for: tokenBanner ) } } uploadStart: { _ in Task { - LucidBanner.shared.update(payload: LucidBannerPayload.Update( + banner?.update(payload: LucidBannerPayload.Update( title: NSLocalizedString("_keep_active_for_upload_", comment: ""), systemImage: "arrowshape.up.circle", imageAnimation: .breathe, @@ -518,14 +520,14 @@ actor NCNetworkingProcess { } } uploadProgressHandler: { _, _, progress in Task { - LucidBanner.shared.update( + banner?.update( payload: LucidBannerPayload.Update(progress: progress), for: tokenBanner ) } } assembling: { Task { - LucidBanner.shared.update(payload: LucidBannerPayload.Update( + banner?.update(payload: LucidBannerPayload.Update( title: NSLocalizedString("_finalizing_wait_", comment: ""), systemImage: "gearshape.arrow.triangle.2.circlepath", imageAnimation: .rotate, @@ -541,7 +543,7 @@ actor NCNetworkingProcess { currentUploadTask = task _ = await task.value - LucidBanner.shared.dismiss() + banner?.dismiss() } // MARK: - Helper diff --git a/iOSClient/Networking/NCService.swift b/iOSClient/Networking/NCService.swift index 7f5b772a63..30ee848ff4 100644 --- a/iOSClient/Networking/NCService.swift +++ b/iOSClient/Networking/NCService.swift @@ -60,15 +60,16 @@ class NCService: NSObject { } switch resultServerStatus.result { case .success(let serverInfo): + let windowScene = SceneManager.shared.getWindowScene(controller: controller) if serverInfo.maintenance { return false } else if serverInfo.productName.lowercased().contains("owncloud") { - await showInfoBanner(controller: controller, + await showInfoBanner(windowScene: windowScene, title: "_warning_", text: "_warning_owncloud_") return false } else if serverInfo.versionMajor <= NCGlobal.shared.nextcloud_unsupported_version { - await showInfoBanner(controller: controller, + await showInfoBanner(windowScene: windowScene, title: "_warning_", text: "_warning_unsupported_") } diff --git a/iOSClient/Notification/NCNotification.swift b/iOSClient/Notification/NCNotification.swift index 1f6f70084e..501cd3f2a5 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -261,7 +261,8 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { self.tableView.reloadData() } else if error != .success { Task { - await showErrorBanner(controller: self.controller, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } else { print("[Error] The user has been changed during networking process.") @@ -308,7 +309,8 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { } } else if error != .success { Task { - await showErrorBanner(controller: self.controller, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } else { print("[Error] The user has been changed during networking process.") diff --git a/iOSClient/RichWorkspace/NCRichWorkspaceCommon.swift b/iOSClient/RichWorkspace/NCRichWorkspaceCommon.swift index f69cf41682..e27640b7fe 100644 --- a/iOSClient/RichWorkspace/NCRichWorkspaceCommon.swift +++ b/iOSClient/RichWorkspace/NCRichWorkspaceCommon.swift @@ -11,7 +11,8 @@ class NCRichWorkspaceCommon: NSObject { func createViewerNextcloudText(serverUrl: String, viewController: UIViewController, controller: NCMainTabBarController?, session: NCSession.Session) { if !NextcloudKit.shared.isNetworkReachable() { Task { - await showErrorBanner(controller: controller, text: "_go_online_", errorCode: NCGlobal.shared.errorOfflineNotAllowed) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: "_go_online_", errorCode: NCGlobal.shared.errorOfflineNotAllowed) } return } @@ -42,7 +43,8 @@ class NCRichWorkspaceCommon: NSObject { } } else if error != .success { Task { - await showErrorBanner(controller: controller, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } @@ -51,7 +53,8 @@ class NCRichWorkspaceCommon: NSObject { func openViewerNextcloudText(serverUrl: String, viewController: UIViewController, controller: NCMainTabBarController?, session: NCSession.Session) { if !NextcloudKit.shared.isNetworkReachable() { Task { - await showErrorBanner(controller: controller, text: "_go_online_", errorCode: NCGlobal.shared.errorOfflineNotAllowed) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: "_go_online_", errorCode: NCGlobal.shared.errorOfflineNotAllowed) } return } @@ -84,7 +87,8 @@ class NCRichWorkspaceCommon: NSObject { } } else if error != .success { Task { - await showErrorBanner(controller: controller, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } diff --git a/iOSClient/Scan document/NCUploadScanDocument.swift b/iOSClient/Scan document/NCUploadScanDocument.swift index 8a8d4f7390..534fc1f84d 100644 --- a/iOSClient/Scan document/NCUploadScanDocument.swift +++ b/iOSClient/Scan document/NCUploadScanDocument.swift @@ -92,7 +92,8 @@ class NCUploadScanDocument: ObservableObject { for char in self.password.unicodeScalars { if !char.isASCII { Task { - await showErrorBanner(controller: self.controller, text: "_password_ascii_", errorCode: 0) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: "_password_ascii_", errorCode: 0) } return DispatchQueue.main.async { completion(true) diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index 889936b7be..168d7e57ae 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -8,9 +8,12 @@ import NextcloudKit import WidgetKit import SwiftUI import CoreLocation +import LucidBanner class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? + var lucidBanner: LucidBanner? + private let appDelegate = UIApplication.shared.delegate as? AppDelegate private var privacyProtectionWindow: UIWindow? private let global = NCGlobal.shared @@ -23,6 +26,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let versionApp = NCUtility().getVersionMaintenance() var lastVersion: String? + lucidBanner = LucidBannerRegistry.shared.banner(for: windowScene) + if let groupDefaults = UserDefaults(suiteName: NCBrandOptions.shared.capabilitiesGroup) { lastVersion = groupDefaults.string(forKey: NCGlobal.shared.udLastVersion) groupDefaults.set(versionApp, forKey: global.udLastVersion) @@ -191,6 +196,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } func sceneDidDisconnect(_ scene: UIScene) { + guard let windowScene = scene as? UIWindowScene else { return } + + LucidBannerRegistry.shared.remove(for: windowScene) + lucidBanner = nil + print("[DEBUG] Scene did disconnect") } @@ -588,6 +598,12 @@ final class SceneManager: @unchecked Sendable { return getWindow(scene: scene) } + func getWindowScene(controller: UITabBarController?) -> UIWindowScene? { + guard let controller = controller as? NCMainTabBarController, + let scene = sceneController[controller] else { return nil } + return getWindow(scene: scene)?.windowScene + } + func getWindow(sceneIdentifier: String?) -> UIWindow? { var mainTabBarController: NCMainTabBarController? @@ -605,6 +621,10 @@ final class SceneManager: @unchecked Sendable { return getWindow(scene: scene) } + func getWindowScene(sceneIdentifier: String?) -> UIWindowScene? { + return getWindowScene(sceneIdentifier: sceneIdentifier) + } + func getSceneIdentifier() -> [String] { var results: [String] = [] for controller in sceneController.keys { diff --git a/iOSClient/Select/NCSelectOpen+SelectDelegate.swift b/iOSClient/Select/NCSelectOpen+SelectDelegate.swift index faa3579793..aab2edfd27 100644 --- a/iOSClient/Select/NCSelectOpen+SelectDelegate.swift +++ b/iOSClient/Select/NCSelectOpen+SelectDelegate.swift @@ -13,7 +13,8 @@ final class NCSelectOpen: NCSelectDelegate { } let error = await NCNetworking.shared.setStatusWaitCopy(metadata, destination: destination, overwrite: overwrite) if error != .success { - await showErrorBanner(controller: controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, error: error) } } @@ -24,7 +25,8 @@ final class NCSelectOpen: NCSelectDelegate { } let error = await NCNetworking.shared.setStatusWaitMove(metadata, destination: destination, overwrite: overwrite) if error != .success { - await showErrorBanner(controller: controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, error: error) } } } diff --git a/iOSClient/Settings/AutoUpload/NCAutoUploadModel.swift b/iOSClient/Settings/AutoUpload/NCAutoUploadModel.swift index a017f4aa35..1430fb6846 100644 --- a/iOSClient/Settings/AutoUpload/NCAutoUploadModel.swift +++ b/iOSClient/Settings/AutoUpload/NCAutoUploadModel.swift @@ -93,7 +93,8 @@ class NCAutoUploadModel: ObservableObject, ViewOnAppearHandling { if value, UIApplication.shared.backgroundRefreshStatus != .available { Task { - await showInfoBanner(controller: controller, + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showInfoBanner(windowScene: windowScene, text: "_access_background_app_refresh_denied_") } } diff --git a/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift b/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift index 1eb80eea56..cd421a04d2 100644 --- a/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift +++ b/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift @@ -18,6 +18,9 @@ class NCEndToEndInitialize: NSObject { var session: NCSession.Session { NCSession.shared.getSession(controller: controller) } + var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } // -------------------------------------------------------------------------------------------- // MARK: Initialize @@ -61,14 +64,14 @@ class NCEndToEndInitialize: NSObject { switch error.errorCode { case NCGlobal.shared.errorBadRequest: Task { - await showInfoBanner(controller: self.controller, + await showInfoBanner(windowScene: self.windowScene, title: "E2E get publicKey", text: "Bad request: internal error") } case NCGlobal.shared.errorResourceNotFound: guard let csr = NCEndToEndEncryption.shared().createCSR(self.session.userId, directory: self.utilityFileSystem.directoryUserData) else { Task { - await showInfoBanner(controller: self.controller, + await showInfoBanner(windowScene: self.windowScene, title: "E2E Csr", text: "Error creating CSR") } @@ -87,7 +90,7 @@ class NCEndToEndInitialize: NSObject { let extractedPublicKey = NCEndToEndEncryption.shared().extractPublicKey(fromCertificate: certificate) if extractedPublicKey != NCEndToEndEncryption.shared().generatedPublicKey { Task { - await showErrorBanner(controller: self.controller, text: "E2E sign publicKey: the public key is incorrect", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E sign publicKey: the public key is incorrect", errorCode: error.errorCode) } } else { NCPreferences().setEndToEndCertificate(account: account, certificate: certificate) @@ -98,22 +101,22 @@ class NCEndToEndInitialize: NSObject { Task { switch error.errorCode { case NCGlobal.shared.errorBadRequest: - await showErrorBanner(controller: self.controller, text: "E2E sign publicKey: bad request: internal error", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E sign publicKey: bad request: internal error", errorCode: error.errorCode) case NCGlobal.shared.errorConflict: - await showErrorBanner(controller: self.controller, text: "E2E sign publicKey: conflict, a public key for the user already exists", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E sign publicKey: conflict, a public key for the user already exists", errorCode: error.errorCode) default: - await showErrorBanner(controller: self.controller, text: "E2E sign publicKey: \(error.errorDescription)", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E sign publicKey: \(error.errorDescription)", errorCode: error.errorCode) } } } } case NCGlobal.shared.errorConflict: Task { - await showErrorBanner(controller: self.controller, text: "E2E get publicKey: forbidden, the user can't access the public keys", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E get publicKey: forbidden, the user can't access the public keys", errorCode: error.errorCode) } default: Task { - await showErrorBanner(controller: self.controller, text: "E2E get publicKey: \(error.errorDescription)", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E get publicKey: \(error.errorDescription)", errorCode: error.errorCode) } } } @@ -159,7 +162,7 @@ class NCEndToEndInitialize: NSObject { NCPreferences().setEndToEndPrivateKey(account: account, privateKey: privateKey) } else { Task { - await showErrorBanner(controller: self.controller, text: "E2E decrypt privateKey: serious internal error to decrypt Private Key", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E decrypt privateKey: serious internal error to decrypt Private Key", errorCode: error.errorCode) } return } @@ -182,7 +185,7 @@ class NCEndToEndInitialize: NSObject { } if verifyCertificate == false { Task { - await showErrorBanner(controller: self.controller, text: "E2E verify certificate server: serious internal error to verify certificate", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E verify certificate server: serious internal error to verify certificate", errorCode: error.errorCode) } return } @@ -196,13 +199,13 @@ class NCEndToEndInitialize: NSObject { Task { switch error.errorCode { case NCGlobal.shared.errorBadRequest: - await showErrorBanner(controller: self.controller, text: "E2E Server publicKey: bad request: internal error", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E Server publicKey: bad request: internal error", errorCode: error.errorCode) case NCGlobal.shared.errorResourceNotFound: - await showErrorBanner(controller: self.controller, text: "E2E Server publicKey: server public key doesn't exist", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E Server publicKey: server public key doesn't exist", errorCode: error.errorCode) case NCGlobal.shared.errorConflict: - await showErrorBanner(controller: self.controller, text: "E2E Server publicKey: forbidden, the user can't access the Server public key", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E Server publicKey: forbidden, the user can't access the Server public key", errorCode: error.errorCode) default: - await showErrorBanner(controller: self.controller, text: "E2E Server publicKey: \(error.errorDescription)", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E Server publicKey: \(error.errorDescription)", errorCode: error.errorCode) } } } @@ -222,7 +225,7 @@ class NCEndToEndInitialize: NSObject { switch error.errorCode { case NCGlobal.shared.errorBadRequest: Task { - await showErrorBanner(controller: self.controller, text: "E2E get privateKey: bad request, internal error", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E get privateKey: bad request, internal error", errorCode: error.errorCode) } case NCGlobal.shared.errorResourceNotFound: // message @@ -242,11 +245,11 @@ class NCEndToEndInitialize: NSObject { self.controller?.present(alertController, animated: true) case NCGlobal.shared.errorConflict: Task { - await showErrorBanner(controller: self.controller, text: "E2E get privateKey: forbidden, the user can't access the private key", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E get privateKey: forbidden, the user can't access the private key", errorCode: error.errorCode) } default: Task { - await showErrorBanner(controller: self.controller, text: "E2E get privateKey: \(error.errorDescription)", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E get privateKey: \(error.errorDescription)", errorCode: error.errorCode) } } } @@ -257,7 +260,7 @@ class NCEndToEndInitialize: NSObject { var privateKeyString: NSString? guard let privateKeyCipher = NCEndToEndEncryption.shared().encryptPrivateKey(session.userId, directory: utilityFileSystem.directoryUserData, passphrase: e2ePassphrase, privateKey: &privateKeyString) else { Task { - await showErrorBanner(controller: self.controller, text: "E2E privateKey: error creating private key cipher", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E privateKey: error creating private key cipher", errorCode: error.errorCode) } return } @@ -293,7 +296,7 @@ class NCEndToEndInitialize: NSObject { } if verifyCertificate == false { Task { - await showErrorBanner(controller: self.controller, text: "E2E verify certificate server: serious internal error to verify certificate", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E verify certificate server: serious internal error to verify certificate", errorCode: error.errorCode) } return } @@ -309,13 +312,13 @@ class NCEndToEndInitialize: NSObject { Task { switch error.errorCode { case NCGlobal.shared.errorBadRequest: - await showErrorBanner(controller: self.controller, text: "E2E Server publicKey: bad request, internal error", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E Server publicKey: bad request, internal error", errorCode: error.errorCode) case NCGlobal.shared.errorResourceNotFound: - await showErrorBanner(controller: self.controller, text: "E2E Server publicKey: server public key doesn't exist", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E Server publicKey: server public key doesn't exist", errorCode: error.errorCode) case NCGlobal.shared.errorConflict: - await showErrorBanner(controller: self.controller, text: "E2E Server publicKey: forbidden, the user can't access the Server public key", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E Server publicKey: forbidden, the user can't access the Server public key", errorCode: error.errorCode) default: - await showErrorBanner(controller: self.controller, text: "E2E Server publicKey: \(error.errorDescription)", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E Server publicKey: \(error.errorDescription)", errorCode: error.errorCode) } } } @@ -324,11 +327,11 @@ class NCEndToEndInitialize: NSObject { Task { switch error.errorCode { case NCGlobal.shared.errorBadRequest: - await showErrorBanner(controller: self.controller, text: "E2E store privateKey: bad request, internal error", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E store privateKey: bad request, internal error", errorCode: error.errorCode) case NCGlobal.shared.errorConflict: - await showErrorBanner(controller: self.controller, text: "E2E store privateKey: conflict, a private key for the user already exists", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E store privateKey: conflict, a private key for the user already exists", errorCode: error.errorCode) default: - await showErrorBanner(controller: self.controller, text: "E2E store privateKey: \(error.errorDescription)", errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: "E2E store privateKey: \(error.errorDescription)", errorCode: error.errorCode) } } } diff --git a/iOSClient/Settings/Settings/SetupPasscodeView.swift b/iOSClient/Settings/Settings/SetupPasscodeView.swift index 21c10a49b1..a579358af8 100644 --- a/iOSClient/Settings/Settings/SetupPasscodeView.swift +++ b/iOSClient/Settings/Settings/SetupPasscodeView.swift @@ -87,8 +87,9 @@ struct SetupPasscodeView: UIViewControllerRepresentable { } else if passcodeSettingsViewController.failedPasscodeAttemptCount == parent.maxFailedAttempts { passcodeSettingsViewController.dismiss(animated: true) Task { + let windowScene = SceneManager.shared.getWindowScene(controller: parent.controller) await showErrorBanner( - controller: parent.controller, + windowScene: windowScene, text: "_too_many_failed_passcode_attempts_error_", errorCode: NCGlobal.shared.errorInternalError ) diff --git a/iOSClient/Share/NCShare+NCCellDelegate.swift b/iOSClient/Share/NCShare+NCCellDelegate.swift index 0eb3b5b481..c1480d67d2 100644 --- a/iOSClient/Share/NCShare+NCCellDelegate.swift +++ b/iOSClient/Share/NCShare+NCCellDelegate.swift @@ -35,7 +35,8 @@ extension NCShare: NCShareLinkCellDelegate, NCShareUserCellDelegate { NCShareCommon.copyLink(link: internalLink, viewController: self, sender: sender) } else { Task { - await showErrorBanner(controller: self.controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: error) } } } diff --git a/iOSClient/Share/NCShareNetworking.swift b/iOSClient/Share/NCShareNetworking.swift index 335c93df26..bb9179126b 100644 --- a/iOSClient/Share/NCShareNetworking.swift +++ b/iOSClient/Share/NCShareNetworking.swift @@ -115,7 +115,8 @@ class NCShareNetworking: NSObject { NCActivityIndicator.shared.stop() } Task { - await showErrorBanner(controller: self.controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: error) } self.delegate?.readShareCompleted() } @@ -170,7 +171,8 @@ class NCShareNetworking: NSObject { } } else { Task { - await showErrorBanner(controller: self.controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: error) } } @@ -201,7 +203,8 @@ class NCShareNetworking: NSObject { } } else { Task { - await showErrorBanner(controller: self.controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: error) } } } @@ -244,7 +247,8 @@ class NCShareNetworking: NSObject { } } else { Task { - await showErrorBanner(controller: self.controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: error) } self.delegate?.updateShareWithError(idShare: shareable.idShare) } @@ -267,7 +271,8 @@ class NCShareNetworking: NSObject { self.delegate?.getSharees(sharees: sharees) } else { Task { - await showErrorBanner(controller: self.controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: error) } self.delegate?.getSharees(sharees: nil) } @@ -295,7 +300,8 @@ class NCShareNetworking: NSObject { self.delegate?.downloadLimitRemoved(by: token) } else { Task { - await showErrorBanner(controller: self.controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: error) } } } @@ -323,7 +329,8 @@ class NCShareNetworking: NSObject { } else { self.delegate?.downloadLimitRemoved(by: token) Task { - await showErrorBanner(controller: self.controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: error) } } } diff --git a/iOSClient/Terms of service/NCTermOfServiceModel.swift b/iOSClient/Terms of service/NCTermOfServiceModel.swift index 334b152b69..e4d7a10861 100644 --- a/iOSClient/Terms of service/NCTermOfServiceModel.swift +++ b/iOSClient/Terms of service/NCTermOfServiceModel.swift @@ -58,7 +58,8 @@ class NCTermOfServiceModel: ObservableObject { delegate.transferReloadDataSource(serverUrl: nil, requestData: true, status: nil) } } else { - await showErrorBanner(controller: controller, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } self.dismissView = true diff --git a/iOSClient/Trash/NCTrash+Networking.swift b/iOSClient/Trash/NCTrash+Networking.swift index 56d0987cf6..b46e89730f 100644 --- a/iOSClient/Trash/NCTrash+Networking.swift +++ b/iOSClient/Trash/NCTrash+Networking.swift @@ -86,7 +86,8 @@ extension NCTrash { } if results.error != .success { - await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) } await self.database.deleteTrashAsync(fileId: nil, account: session.account) await self.reloadDataSource() @@ -107,7 +108,8 @@ extension NCTrash { } } if results.error != .success { - await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) } await self.database.deleteTrashAsync(fileId: fileId, account: session.account) await self.reloadDataSource() diff --git a/iOSClient/UserStatus/NCUserStatusModel.swift b/iOSClient/UserStatus/NCUserStatusModel.swift index 55f50059b2..cfd47f106f 100644 --- a/iOSClient/UserStatus/NCUserStatusModel.swift +++ b/iOSClient/UserStatus/NCUserStatusModel.swift @@ -49,7 +49,8 @@ import NextcloudKit if result.error == .success { selectedStatus = result.status } else { - await showErrorBanner(controller: self.controller, error: result.error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: result.error) } } } @@ -65,7 +66,8 @@ import NextcloudKit } if result.error != .success { - await showErrorBanner(controller: self.controller, error: result.error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: result.error) } } } @@ -80,7 +82,8 @@ import NextcloudKit } if result.error != .success { - await showErrorBanner(controller: self.controller, error: result.error) + let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: result.error) } await NCManageDatabase.shared.setAccountUserStatusAsync(userStatusClearAt: result.clearAt, diff --git a/iOSClient/Viewer/NCViewer.swift b/iOSClient/Viewer/NCViewer.swift index 5729b80ae5..5008e06c8c 100644 --- a/iOSClient/Viewer/NCViewer.swift +++ b/iOSClient/Viewer/NCViewer.swift @@ -84,7 +84,8 @@ class NCViewer: NSObject { NCActivityIndicator.shared.stop() guard results.error == .success, let url = results.url else { - await showErrorBanner(controller: delegate?.tabBarController as? NCMainTabBarController, text: results.error.errorDescription, errorCode: results.error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: delegate?.tabBarController as? NCMainTabBarController) + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) return nil } @@ -138,7 +139,8 @@ class NCViewer: NSObject { NCActivityIndicator.shared.stop() guard results.error == .success, let url = results.url else { - await showErrorBanner(controller: delegate?.tabBarController as? NCMainTabBarController, text: results.error.errorDescription, errorCode: results.error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(controller: delegate?.tabBarController as? NCMainTabBarController) + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) return nil } diff --git a/iOSClient/Viewer/NCViewerProviderContextMenu.swift b/iOSClient/Viewer/NCViewerProviderContextMenu.swift index 219dd590f3..ec6f4b1479 100644 --- a/iOSClient/Viewer/NCViewerProviderContextMenu.swift +++ b/iOSClient/Viewer/NCViewerProviderContextMenu.swift @@ -236,7 +236,8 @@ extension NCViewerProviderContextMenu: VLCMediaPlayerDelegate { case .error: NCActivityIndicator.shared.stop() Task { - await showErrorBanner(sceneIdentifier: self.sceneIdentifier, text: "_error_something_wrong_", errorCode: 0) + let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: self.sceneIdentifier) + await showErrorBanner(windowScene: windowScene, text: "_error_something_wrong_", errorCode: 0) } print("Played mode: ERROR") case .playing: @@ -294,7 +295,8 @@ extension NCViewerProviderContextMenu: NCTransferDelegate { error: NKError) { if error != .success { Task { - await showErrorBanner(sceneIdentifier: self.sceneIdentifier, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: self.sceneIdentifier) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } diff --git a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLookView.swift b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLookView.swift index 816b467464..608800321f 100644 --- a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLookView.swift +++ b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLookView.swift @@ -29,7 +29,8 @@ struct NCViewerQuickLookView: UIViewControllerRepresentable { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { if model.previewStore[index].assetType == .livePhoto && model.previewStore[index].asset.type == .livePhoto && model.previewStore[index].data == nil { Task { - await showInfoBanner(controller: self.model.controller, text: "_message_disable_livephoto_") + let windowScene = SceneManager.shared.getWindowScene(controller: self.model.controller) + await showInfoBanner(windowScene: windowScene, text: "_message_disable_livephoto_") } } } diff --git a/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift b/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift index 47ca93a11c..e0d8f5d2fc 100644 --- a/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift +++ b/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift @@ -259,7 +259,8 @@ class NCViewerRichDocument: UIViewController, WKNavigationDelegate, WKScriptMess } } else { Task { - await showErrorBanner(sceneIdentifier: self.sceneIdentifier, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: self.sceneIdentifier) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } }) @@ -321,7 +322,8 @@ class NCViewerRichDocument: UIViewController, WKNavigationDelegate, WKScriptMess self.webView.evaluateJavaScript(functionJS, completionHandler: { _, _ in }) } else { Task { - await showErrorBanner(sceneIdentifier: self.sceneIdentifier, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: self.sceneIdentifier) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } @@ -344,7 +346,8 @@ class NCViewerRichDocument: UIViewController, WKNavigationDelegate, WKScriptMess self.webView.evaluateJavaScript(functionJS, completionHandler: { _, _ in }) } else { Task { - await showErrorBanner(sceneIdentifier: self.sceneIdentifier, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: self.sceneIdentifier) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } From 8e13cdcafab6b4def02282c4b43b5c21bfc62209 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 09:01:45 +0100 Subject: [PATCH 03/20] code Signed-off-by: Marino Faggiana --- iOSClient/GUI/Lucid Banner/BannerView.swift | 7 ++-- iOSClient/Main/NCDragDrop.swift | 35 +++++++++++-------- iOSClient/Main/NCPickerViewController.swift | 9 +++-- iOSClient/Networking/NCAutoUpload.swift | 27 +++++++------- iOSClient/Networking/NCConfigServer.swift | 7 ++-- .../NCNetworking+NextcloudKitDelegate.swift | 24 ++++++++----- .../NCNetworking+TransferDelegate.swift | 27 ++++++++++---- iOSClient/Select/NCSelect.swift | 8 +++-- .../Settings/E2EE/NCManageE2EEModel.swift | 3 ++ .../Settings/E2EE/NCManageE2EEView.swift | 20 +++++++---- .../Advanced/NCShareAdvancePermission.swift | 7 ++-- .../StatusMessage/NCStatusMessageModel.swift | 12 ++++--- .../Utility/NCOperationSaveLivePhoto.swift | 25 ++++++------- .../NCPlayer/NCPlayerToolBar.swift | 19 +++++----- .../Viewer/NCViewerMedia/NCViewerMedia.swift | 15 ++++---- .../NCViewerQuickLook/NCViewerQuickLook.swift | 2 +- 16 files changed, 152 insertions(+), 95 deletions(-) diff --git a/iOSClient/GUI/Lucid Banner/BannerView.swift b/iOSClient/GUI/Lucid Banner/BannerView.swift index f7fa9bc344..3f051a835a 100644 --- a/iOSClient/GUI/Lucid Banner/BannerView.swift +++ b/iOSClient/GUI/Lucid Banner/BannerView.swift @@ -8,6 +8,7 @@ import NextcloudKit import Alamofire // MARK: - Show Banner +@discardableResult @MainActor func showBanner(windowScene: UIWindowScene?, title: String?, @@ -21,9 +22,9 @@ func showBanner(windowScene: UIWindowScene?, backgroundColor: UIColor, autoDismissAfter: TimeInterval = NCGlobal.shared.dismissAfterSecond, swipeToDismiss: Bool = true, - policy: LucidBanner.ShowPolicy = .enqueue) async { + policy: LucidBanner.ShowPolicy = .enqueue) async -> LucidBanner? { guard let windowScene else { - return + return nil } let payload = LucidBannerPayload( @@ -47,6 +48,8 @@ func showBanner(windowScene: UIWindowScene?, policy: policy) { state in MessageBannerView(state: state) } + + return banner } // MARK: - Show Info diff --git a/iOSClient/Main/NCDragDrop.swift b/iOSClient/Main/NCDragDrop.swift index 215ef1933e..a21bc2f353 100644 --- a/iOSClient/Main/NCDragDrop.swift +++ b/iOSClient/Main/NCDragDrop.swift @@ -145,7 +145,8 @@ class NCDragDrop: NSObject { database.addMetadata(metadataForUpload) } catch { Task { - await showErrorBanner(controller: controller, text: error.localizedDescription, errorCode: NCGlobal.shared.errorInternalError) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: error.localizedDescription, errorCode: NCGlobal.shared.errorInternalError) } return } @@ -166,7 +167,8 @@ class NCDragDrop: NSObject { error: .success) } } else { - await showErrorBanner(controller: controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, error: error) } } } @@ -186,19 +188,21 @@ class NCDragDrop: NSObject { error: .success) } } else { - await showErrorBanner(controller: controller, error: error) + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, error: error) } } } @MainActor func transfers(collectionViewCommon: NCCollectionViewCommon, destination: String, session: NCSession.Session) async { + var token: Int? + var banner: LucidBanner? defer { - LucidBanner.shared.dismiss() + banner?.dismiss() } - let scene = SceneManager.shared.getWindow(sceneIdentifier: collectionViewCommon.controller?.sceneIdentifier)?.windowScene guard let metadatas = DragDropHover.shared.sourceMetadatas, - let window = scene?.windows.first else { + let window = SceneManager.shared.getWindow(sceneIdentifier: collectionViewCommon.controller?.sceneIdentifier) else { return } var uploadRequest: UploadRequest? @@ -212,10 +216,10 @@ class NCDragDrop: NSObject { horizontalLayout: horizontalLayout, blocksTouches: false, draggable: false) - let token = showUploadBanner(scene: scene, - payload: payload, - allowMinimizeOnTap: false, - onButtonTap: { + (token, banner) = showUploadBanner(windowScene: window.windowScene, + payload: payload, + allowMinimizeOnTap: false, + onButtonTap: { if let downloadRequest { downloadRequest.cancel() } else if let uploadRequest { @@ -229,7 +233,7 @@ class NCDragDrop: NSObject { systemImage: "arrow.left.arrow.right.circle", imageAnimation: .pulsebyLayer, ) - LucidBanner.shared.update(payload: payloadUpdate) + banner?.update(payload: payloadUpdate) for (index, metadata) in metadatas.enumerated() { if metadata.directory { @@ -245,7 +249,9 @@ class NCDragDrop: NSObject { downloadRequest = request } guard results.nkError == .success else { - await showErrorBanner(scene: scene, text: results.nkError.errorDescription, errorCode: results.nkError.errorCode) + await showErrorBanner(windowScene: window.windowScene, + text: results.nkError.errorDescription, + errorCode: results.nkError.errorCode) break } } @@ -267,11 +273,12 @@ class NCDragDrop: NSObject { uploadRequest = request } guard results.error == .success else { - await showErrorBanner(scene: scene, text: results.error.errorDescription, errorCode: results.error.errorCode) + await showErrorBanner(windowScene: window.windowScene, + text: results.error.errorDescription, errorCode: results.error.errorCode) break } - LucidBanner.shared.update( + banner?.update( payload: LucidBannerPayload.Update(progress: Double(index + 1) / Double(metadatas.count)), for: token) } diff --git a/iOSClient/Main/NCPickerViewController.swift b/iOSClient/Main/NCPickerViewController.swift index 5a007289f0..76a866dbd2 100644 --- a/iOSClient/Main/NCPickerViewController.swift +++ b/iOSClient/Main/NCPickerViewController.swift @@ -16,6 +16,9 @@ class NCPhotosPickerViewController: NSObject { var maxSelectedAssets = 1 var singleSelectedMode = false let global = NCGlobal.shared + var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } @discardableResult init(controller: NCMainTabBarController, maxSelectedAssets: Int, singleSelectedMode: Bool) { @@ -60,19 +63,19 @@ class NCPhotosPickerViewController: NSObject { pickerVC?.didExceedMaximumNumberOfSelection = { _ in Task { - await showErrorBanner(controller: self.controller, text: "_limited_dimension_", errorCode: 0) + await showErrorBanner(windowScene: self.windowScene, text: "_limited_dimension_", errorCode: 0) } } pickerVC?.handleNoAlbumPermissions = { _ in Task { - await showErrorBanner(controller: self.controller, text: "_denied_album_", errorCode: 0) + await showErrorBanner(windowScene: self.windowScene, text: "_denied_album_", errorCode: 0) } } pickerVC?.handleNoCameraPermissions = { _ in Task { - await showErrorBanner(controller: self.controller, text: "_denied_camera_", errorCode: 0) + await showErrorBanner(windowScene: self.windowScene, text: "_denied_camera_", errorCode: 0) } } diff --git a/iOSClient/Networking/NCAutoUpload.swift b/iOSClient/Networking/NCAutoUpload.swift index f2fa4aee29..e894803b69 100644 --- a/iOSClient/Networking/NCAutoUpload.swift +++ b/iOSClient/Networking/NCAutoUpload.swift @@ -42,26 +42,27 @@ class NCAutoUpload: NSObject { model: NCAutoUploadModel, assetCollections: [PHAssetCollection], account: String) async { + let windowScene = SceneManager.shared.getWindowScene(controller: controller) + var banner: LucidBanner? defer { - LucidBanner.shared.dismiss(after: 1) + banner?.dismiss(after: 1) } guard let tblAccount = await self.database.getTableAccountAsync(predicate: NSPredicate(format: "account == %@", account)) else { return } - let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene - await showBanner(scene: scene, - title: "_info_", - subtitle: "_creating_db_photo_progress_", - textColor: .label, - image: "photo.on.rectangle.angled", - imageAnimation: .bounce, - imageColor: .label, - backgroundColor: UIColor.lightGray.withAlphaComponent(0.75), - autoDismissAfter: 0, - swipeToDismiss: false, - policy: .drop + banner = await showBanner(windowScene: windowScene, + title: "_info_", + subtitle: "_creating_db_photo_progress_", + textColor: .label, + image: "photo.on.rectangle.angled", + imageAnimation: .bounce, + imageColor: .label, + backgroundColor: UIColor.lightGray.withAlphaComponent(0.75), + autoDismissAfter: 0, + swipeToDismiss: false, + policy: .drop ) let result = await getCameraRollAssets(controller: controller, assetCollections: assetCollections, tblAccount: tblAccount) diff --git a/iOSClient/Networking/NCConfigServer.swift b/iOSClient/Networking/NCConfigServer.swift index bfb6a4b845..2e15bf68e9 100644 --- a/iOSClient/Networking/NCConfigServer.swift +++ b/iOSClient/Networking/NCConfigServer.swift @@ -12,6 +12,9 @@ import NextcloudKit final class NCConfigServer: NSObject, UIActionSheetDelegate, URLSessionDelegate { let controller: NCMainTabBarController? + var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } init(controller: NCMainTabBarController?) { self.controller = controller @@ -29,7 +32,7 @@ final class NCConfigServer: NSObject, UIActionSheetDelegate, URLSessionDelegate let dataTask = defaultSession.dataTask(with: urlRequest) { data, _, error in if let error { Task { - await showErrorBanner(controller: self.controller, error: NKError(error: error)) + await showErrorBanner(windowScene: self.windowScene, error: NKError(error: error)) } } else if let data = data { self.start(data: data) @@ -79,7 +82,7 @@ final class NCConfigServer: NSObject, UIActionSheetDelegate, URLSessionDelegate UIApplication.shared.open(url) } catch { Task { - await showErrorBanner(controller: self.controller, error: NKError(error: error)) + await showErrorBanner(windowScene: self.windowScene, error: NKError(error: error)) } self.stop() } diff --git a/iOSClient/Networking/NCNetworking+NextcloudKitDelegate.swift b/iOSClient/Networking/NCNetworking+NextcloudKitDelegate.swift index 85582d9e2c..cc2ebf5d4a 100644 --- a/iOSClient/Networking/NCNetworking+NextcloudKitDelegate.swift +++ b/iOSClient/Networking/NCNetworking+NextcloudKitDelegate.swift @@ -6,6 +6,7 @@ import Foundation import UIKit import NextcloudKit import Alamofire +import LucidBanner extension NCNetworking { @@ -15,16 +16,21 @@ extension NCNetworking { lastReachability = true } else { if lastReachability { + let windowScenes = UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .filter { $0.activationState == .foregroundActive || $0.activationState == .foregroundInactive } Task { - await showBannerActiveScenes( - title: "_info_", - subtitle: "_network_not_available_", - textColor: .label, - image: "wifi.exclamationmark.circle", - imageAnimation: .bounce, - imageColor: .label, - backgroundColor: UIColor.lightGray.withAlphaComponent(0.75) - ) + for windowScene in windowScenes { + await showBanner(windowScene: windowScene, + title: "_info_", + subtitle: "_network_not_available_", + textColor: .label, + image: "wifi.exclamationmark.circle", + imageAnimation: .bounce, + imageColor: .label, + backgroundColor: UIColor.lightGray.withAlphaComponent(0.75)) + + } } } lastReachability = false diff --git a/iOSClient/Networking/NCNetworking+TransferDelegate.swift b/iOSClient/Networking/NCNetworking+TransferDelegate.swift index e5fc79c672..7267f35c3a 100644 --- a/iOSClient/Networking/NCNetworking+TransferDelegate.swift +++ b/iOSClient/Networking/NCNetworking+TransferDelegate.swift @@ -54,7 +54,7 @@ extension NCNetworking: NCTransferDelegate { } } guard let controller else { return } - let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene + let windowScene = SceneManager.shared.getWindowScene(controller: controller) switch selector { case NCGlobal.shared.selectorLoadFileQuickLook: @@ -121,7 +121,9 @@ extension NCNetworking: NCTransferDelegate { NCAskAuthorization().askAuthorizationPhotoLibrary(controller: controller) { hasPermission in guard hasPermission else { Task {@MainActor in - await showErrorBanner(scene: scene, text: "_access_photo_not_enabled_msg_", errorCode: 0) + await showErrorBanner(windowScene: windowScene, + text: "_access_photo_not_enabled_msg_", + errorCode: NCGlobal.shared.errorInternalError) } return } @@ -135,7 +137,9 @@ extension NCNetworking: NCTransferDelegate { }) { success, _ in if !success { Task { - await showErrorBanner(scene: scene, text: "_file_not_saved_cameraroll_", errorCode: 0) + await showErrorBanner(windowScene: windowScene, + text: "_file_not_saved_cameraroll_", + errorCode: NCGlobal.shared.errorInternalError) } } } @@ -145,19 +149,25 @@ extension NCNetworking: NCTransferDelegate { }) { success, _ in if !success { Task { - await showErrorBanner(scene: scene, text: "_file_not_saved_cameraroll_", errorCode: 0) + await showErrorBanner(windowScene: windowScene, + text: "_file_not_saved_cameraroll_", + errorCode: NCGlobal.shared.errorInternalError) } } } } else { Task { - await showErrorBanner(scene: scene, text: "_file_not_saved_cameraroll_", errorCode: 0) + await showErrorBanner(windowScene: windowScene, + text: "_file_not_saved_cameraroll_", + errorCode: NCGlobal.shared.errorInternalError) } return } } catch { Task { - await showErrorBanner(scene: scene, text: "_file_not_saved_cameraroll_", errorCode: 0) + await showErrorBanner(windowScene: windowScene, + text: "_file_not_saved_cameraroll_", + errorCode: NCGlobal.shared.errorInternalError) } } } @@ -195,6 +205,7 @@ extension NCNetworking: NCTransferDelegate { @MainActor func viewerFile(account: String, fileId: String, viewController: UIViewController) async { + let windowScene = SceneManager.shared.getWindowScene(controller: viewController.tabBarController) if let metadata = await NCManageDatabase.shared.getMetadataFromFileIdAsync(fileId) { do { let attr = try FileManager.default.attributesOfItem(atPath: utilityFileSystem.getDirectoryProviderStorageOcId( @@ -227,7 +238,9 @@ extension NCNetworking: NCTransferDelegate { } guard resultsFile.error == .success, let file = resultsFile.file else { Task { - await showErrorBanner(controller: viewController.tabBarController, text: resultsFile.error.errorDescription, errorCode: resultsFile.error.errorCode) + await showErrorBanner(windowScene: windowScene, + text: resultsFile.error.errorDescription, + errorCode: resultsFile.error.errorCode) } return } diff --git a/iOSClient/Select/NCSelect.swift b/iOSClient/Select/NCSelect.swift index 51edc368f3..2fd087ab2f 100644 --- a/iOSClient/Select/NCSelect.swift +++ b/iOSClient/Select/NCSelect.swift @@ -82,6 +82,9 @@ class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresent private var backgroundImageView = UIImageView() var sceneIdentifier: String = "" + var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } // MARK: - View Life Cycle @@ -229,7 +232,8 @@ class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresent error: NKError) { if error != .success { Task { - await showErrorBanner(sceneIdentifier: sceneIdentifier, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: sceneIdentifier) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } @@ -273,7 +277,7 @@ class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresent let alertController = UIAlertController.createFolderWith(serverUrl: serverUrl, session: session, capabilities: capabilities) { error in if error != .success { Task { - await showErrorBanner(controller: self.controller, + await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) } diff --git a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift index e62079b62d..34231ca918 100644 --- a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift +++ b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift @@ -22,6 +22,9 @@ class NCManageE2EE: NSObject, ObservableObject, ViewOnAppearHandling, NCEndToEnd var capabilities: NKCapabilities.Capabilities { NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() } + var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } init(controller: NCMainTabBarController?) { super.init() diff --git a/iOSClient/Settings/E2EE/NCManageE2EEView.swift b/iOSClient/Settings/E2EE/NCManageE2EEView.swift index f1a89fded7..6bd7448567 100644 --- a/iOSClient/Settings/E2EE/NCManageE2EEView.swift +++ b/iOSClient/Settings/E2EE/NCManageE2EEView.swift @@ -44,7 +44,7 @@ struct NCManageE2EEView: View { model.requestPasscodeType("readPassphrase") } else { Task { - await showInfoBanner(controller: model.controller, text: "_e2e_settings_lock_not_active_") + await showInfoBanner(windowScene: model.windowScene, text: "_e2e_settings_lock_not_active_") } } } @@ -67,7 +67,7 @@ struct NCManageE2EEView: View { model.requestPasscodeType("removeLocallyEncryption") } else { Task { - await showInfoBanner(controller: model.controller, text: "_e2e_settings_lock_not_active_") + await showInfoBanner(windowScene: model.windowScene, text: "_e2e_settings_lock_not_active_") } } } @@ -97,7 +97,7 @@ struct NCManageE2EEView: View { model.requestPasscodeType("startE2E") } else { Task { - await showInfoBanner(controller: model.controller, text: "_e2e_settings_lock_not_active_") + await showInfoBanner(windowScene: model.windowScene, text: "_e2e_settings_lock_not_active_") } } } @@ -146,9 +146,12 @@ struct NCManageE2EEView: View { } completion: { _, _, error in Task { if error == .success { - await showInfoBanner(controller: model.controller, text: "E2E delete certificate") + await showInfoBanner(windowScene: model.windowScene, + text: "E2E delete certificate") } else { - await showErrorBanner(controller: model.controller, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: model.windowScene, + text: error.errorDescription, + errorCode: error.errorCode) } } } @@ -177,9 +180,12 @@ struct NCManageE2EEView: View { } completion: { _, _, error in Task { if error == .success { - await showInfoBanner(controller: model.controller, text: "E2E delete privateKey") + await showInfoBanner(windowScene: model.windowScene, + text: "E2E delete privateKey") } else { - await showErrorBanner(controller: model.controller, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: model.windowScene, + text: error.errorDescription, + errorCode: error.errorCode) } } } diff --git a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift index efe80bc90c..be2bc64ba2 100644 --- a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift +++ b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift @@ -57,6 +57,9 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg var networking: NCShareNetworking? var controller: NCMainTabBarController? + var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } override func viewDidLoad() { super.viewDidLoad() @@ -249,7 +252,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg NCGlobal.shared.isE2eeVersion2(capabilities.e2EEApiVersion) { if await NCNetworkingE2EE().isInUpload(account: metadata.account, serverUrl: metadata.serverUrlFileName) { - await showErrorBanner(controller: controller, + await showErrorBanner(windowScene: windowScene, text: "_e2e_in_upload_", errorCode: NCGlobal.shared.errorE2EEUploadInProgress) return @@ -258,7 +261,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: metadata.serverUrlFileName, addUserId: share.shareWith, removeUserId: nil, account: metadata.account) if error != .success { - await showErrorBanner(controller: controller, error: error) + await showErrorBanner(windowScene: windowScene, error: error) } } diff --git a/iOSClient/StatusMessage/NCStatusMessageModel.swift b/iOSClient/StatusMessage/NCStatusMessageModel.swift index 10b08726d0..b01ea047b7 100644 --- a/iOSClient/StatusMessage/NCStatusMessageModel.swift +++ b/iOSClient/StatusMessage/NCStatusMessageModel.swift @@ -24,6 +24,10 @@ import NextcloudKit var statusText: String = "" var clearAfterString = "_dont_clear_" + var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } + init(controller: NCMainTabBarController?) { self.controller = controller } @@ -61,7 +65,7 @@ import NextcloudKit } if result.error != .success { - await showErrorBanner(controller: self.controller, error: result.error) + await showErrorBanner(windowScene: self.windowScene, error: result.error) } } } @@ -78,7 +82,7 @@ import NextcloudKit if result.error == .success { predefinedStatuses = isXcodeRunningForPreviews ? createStatusesForPreview() : result.userStatuses ?? [] } else { - await showErrorBanner(controller: self.controller, error: result.error) + await showErrorBanner(windowScene: self.windowScene, error: result.error) } } } @@ -93,7 +97,7 @@ import NextcloudKit } if result.error != .success { - await showErrorBanner(controller: self.controller, error: result.error) + await showErrorBanner(windowScene: self.windowScene, error: result.error) } } } @@ -118,7 +122,7 @@ import NextcloudKit userStatusStatusIsUserDefined: result.statusIsUserDefined, account: result.account) } else { - await showErrorBanner(controller: self.controller, error: result.error) + await showErrorBanner(windowScene: self.windowScene, error: result.error) } } } diff --git a/iOSClient/Utility/NCOperationSaveLivePhoto.swift b/iOSClient/Utility/NCOperationSaveLivePhoto.swift index c4aaa77ecf..82e6ca2c63 100644 --- a/iOSClient/Utility/NCOperationSaveLivePhoto.swift +++ b/iOSClient/Utility/NCOperationSaveLivePhoto.swift @@ -11,13 +11,14 @@ class NCOperationSaveLivePhoto: ConcurrentOperation, @unchecked Sendable { var metadata: tableMetadata var metadataMOV: tableMetadata let utilityFileSystem = NCUtilityFileSystem() - var scene: UIWindowScene? + var windowScene: UIWindowScene? var tokenBanner: Int? + var banner: LucidBanner? init(metadata: tableMetadata, metadataMOV: tableMetadata, controller: UITabBarController?) { self.metadata = tableMetadata.init(value: metadata) self.metadataMOV = tableMetadata.init(value: metadataMOV) - scene = SceneManager.shared.getWindow(controller: controller)?.windowScene + windowScene = SceneManager.shared.getWindowScene(controller: controller) } override func start() { @@ -31,12 +32,12 @@ class NCOperationSaveLivePhoto: ConcurrentOperation, @unchecked Sendable { selector: "") else { return self.finish() } - tokenBanner = showHudBanner(scene: scene, title: "_download_image_") + (tokenBanner, banner) = showHudBanner(windowScene: windowScene, title: "_download_image_") let resultsMetadata = await NCNetworking.shared.downloadFile(metadata: metadata) { _ in } progressHandler: { progress in Task {@MainActor in - LucidBanner.shared.update( + self.banner?.update( payload: LucidBannerPayload.Update(progress: progress.fractionCompleted), for: self.tokenBanner) } @@ -44,7 +45,7 @@ class NCOperationSaveLivePhoto: ConcurrentOperation, @unchecked Sendable { guard resultsMetadata.nkError == .success else { Task {@MainActor in - completeHudBannerError(description: "_livephoto_save_error_", token: self.tokenBanner) + completeHudBannerError(description: "_livephoto_save_error_", token: self.tokenBanner, banner: self.banner) } return self.finish() } @@ -52,7 +53,7 @@ class NCOperationSaveLivePhoto: ConcurrentOperation, @unchecked Sendable { let resultsMetadataLive = await NCNetworking.shared.downloadFile(metadata: metadataLive) { _ in } progressHandler: { progress in Task {@MainActor in - LucidBanner.shared.update( + self.banner?.update( payload: LucidBannerPayload.Update(progress: progress.fractionCompleted), for: self.tokenBanner) } @@ -60,7 +61,7 @@ class NCOperationSaveLivePhoto: ConcurrentOperation, @unchecked Sendable { guard resultsMetadataLive.nkError == .success else { Task {@MainActor in - completeHudBannerError(description: "_livephoto_save_error_", token: self.tokenBanner) + completeHudBannerError(description: "_livephoto_save_error_", token: self.tokenBanner, banner: self.banner) } return self.finish() } @@ -84,11 +85,11 @@ class NCOperationSaveLivePhoto: ConcurrentOperation, @unchecked Sendable { let payload = LucidBannerPayload.Update( title: NSLocalizedString("_livephoto_save_", comment: ""), ) - LucidBanner.shared.update(payload: payload, for: self.tokenBanner) + banner?.update(payload: payload, for: self.tokenBanner) NCLivePhoto.generate(from: fileNameImage, videoURL: fileNameMov, progress: { progress in Task {@MainActor in - LucidBanner.shared.update( + self.banner?.update( payload: LucidBannerPayload.Update(progress: progress), for: self.tokenBanner) } @@ -97,16 +98,16 @@ class NCOperationSaveLivePhoto: ConcurrentOperation, @unchecked Sendable { NCLivePhoto.saveToLibrary(resources) { result in Task {@MainActor in if !result { - completeHudBannerError(description: "_livephoto_save_error_", token: self.tokenBanner) + completeHudBannerError(description: "_livephoto_save_error_", token: self.tokenBanner, banner: self.banner) } else { - completeHudBannerSuccess(token: self.tokenBanner) + completeHudBannerSuccess(token: self.tokenBanner, banner: self.banner) } return self.finish() } } } else { Task {@MainActor in - completeHudBannerError(description: "_livephoto_save_error_", token: self.tokenBanner) + completeHudBannerError(description: "_livephoto_save_error_", token: self.tokenBanner, banner: self.banner) return self.finish() } } diff --git a/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift b/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift index 3e01bb1154..e70541add3 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift @@ -340,15 +340,15 @@ extension NCPlayerToolBar: NCSelectDelegate { func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], overwrite: Bool, copy: Bool, move: Bool, session: NCSession.Session, controller: NCMainTabBarController?) { if let metadata = metadata, let viewerMediaPage = viewerMediaPage { let fileNameLocalPath = NCUtilityFileSystem().getDirectoryProviderStorageOcId(metadata.ocId, fileName: metadata.fileNameView, userId: metadata.userId, urlBase: metadata.urlBase) - let scene = SceneManager.shared.getWindow(controller: viewerMediaPage.tabBarController)?.windowScene + let windowScene = SceneManager.shared.getWindowScene(controller: viewerMediaPage.tabBarController) if utilityFileSystem.fileProviderStorageExists(metadata) { addPlaybackSlave(type: type, metadata: metadata) } else { var downloadRequest: DownloadRequest? - let token = showHudBanner(scene: scene, - title: "_download_in_progress_", - stage: .button) { + let (token, banner) = showHudBanner(windowScene: windowScene, + title: "_download_in_progress_", + stage: .button) { if let request = downloadRequest { request.cancel() } @@ -370,13 +370,12 @@ extension NCPlayerToolBar: NCSelectDelegate { } }, progressHandler: { progress in Task {@MainActor in - LucidBanner.shared.update( - payload: LucidBannerPayload.Update(progress: Double(progress.fractionCompleted)), - for: token) + banner?.update(payload: LucidBannerPayload.Update(progress: Double(progress.fractionCompleted)), + for: token) } }) { _, etag, _, _, _, _, error in Task { - LucidBanner.shared.dismiss() + banner?.dismiss() let ocId = metadata.ocId await self.database.setMetadataSessionAsync(ocId: ocId, @@ -389,7 +388,9 @@ extension NCPlayerToolBar: NCSelectDelegate { if error == .success { self.addPlaybackSlave(type: type, metadata: metadata) } else if error.errorCode != 200 { - await showErrorBanner(scene: scene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, + text: error.errorDescription, + errorCode: error.errorCode) } } } diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift index 047b7d8a77..e1cc3b130f 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift @@ -157,11 +157,11 @@ class NCViewerMedia: UIViewController { selector: "") else { return } - let scene = SceneManager.shared.getWindow(controller: self.tabBarController)?.windowScene + let windowScene = SceneManager.shared.getWindowScene(controller: self.tabBarController) var downloadRequest: DownloadRequest? - let token = showHudBanner(scene: scene, - title: "_download_in_progress_", - stage: .button) { + let (token, banner) = showHudBanner(windowScene: windowScene, + title: "_download_in_progress_", + stage: .button) { if let request = downloadRequest { request.cancel() } @@ -171,12 +171,11 @@ class NCViewerMedia: UIViewController { downloadRequest = request } progressHandler: { progress in Task {@MainActor in - LucidBanner.shared.update( - payload: LucidBannerPayload.Update(progress: progress.fractionCompleted), - for: token) + banner?.update(payload: LucidBannerPayload.Update(progress: progress.fractionCompleted), + for: token) } } - LucidBanner.shared.dismiss() + banner?.dismiss() if results.nkError == .success { if self.utilityFileSystem.fileProviderStorageExists(self.metadata) { diff --git a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift index 23da247204..37dabd4a1a 100644 --- a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift +++ b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift @@ -65,7 +65,7 @@ private var hasChangesQuickLook: Bool = false if metadata?.isLivePhoto == true { Task { - await showErrorBannerActiveScenes(text: "_message_disable_overwrite_livephoto_", errorCode: NCGlobal.shared.errorInternalError) + // await showErrorBannerActiveScenes(text: "_message_disable_overwrite_livephoto_", errorCode: NCGlobal.shared.errorInternalError) } } From 7e547cb01a64c5a21b364db935c2407ca13cd2a8 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 09:09:58 +0100 Subject: [PATCH 04/20] update Signed-off-by: Marino Faggiana --- iOSClient/Login/NCLogin.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/iOSClient/Login/NCLogin.swift b/iOSClient/Login/NCLogin.swift index d4dcf1193d..94d6dba521 100644 --- a/iOSClient/Login/NCLogin.swift +++ b/iOSClient/Login/NCLogin.swift @@ -46,6 +46,9 @@ class NCLogin: UIViewController, UITextFieldDelegate, NCLoginQRCodeDelegate { private var QRCodeCheck: Bool = false private var activeLoginProvider: NCLoginProvider? + // LucidBanner + var banner: LucidBanner? + // MARK: - View Life Cycle override func viewDidLoad() { @@ -188,24 +191,24 @@ class NCLogin: UIViewController, UITextFieldDelegate, NCLoginQRCodeDelegate { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - if self.shareAccounts != nil { + if self.shareAccounts != nil, + let windowScene = view.window?.windowScene { let title = String(format: NSLocalizedString("_apps_nextcloud_detect_", comment: ""), NCBrandOptions.shared.brand) let subtitle = String(format: NSLocalizedString("_add_existing_account_", comment: ""), NCBrandOptions.shared.brand) + self.banner = LucidBannerRegistry.shared.banner(for: windowScene) - /* - showAlertActionBannerView(w: view.window?.windowScene, + showAlertActionBannerView(lucidBanner: lucidBanner, title: title, subtitle: subtitle) { self.openShareAccountsViewController(nil) } - */ } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - // LucidBanner.shared.dismiss() + self.banner?.dismiss() } private func handleLoginWithAppConfig() { From d5ad3fb459d1ddf541f71aac9973b8fdb8d813c7 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 09:50:32 +0100 Subject: [PATCH 05/20] clean Signed-off-by: Marino Faggiana --- .../Assistant/Chat/NCAssistantChatModel.swift | 7 +- iOSClient/Main/NCPickerViewController.swift | 1 + iOSClient/Menu/NCContextMenuMain.swift | 11 +-- iOSClient/Menu/NCContextMenuProfile.swift | 6 +- .../Networking/NCNetworking+ServerError.swift | 2 +- .../Networking/NCNetworking+Upload.swift | 2 +- iOSClient/Networking/NCService.swift | 2 +- .../RichWorkspace/NCRichWorkspaceCommon.swift | 8 +-- iOSClient/SceneDelegate.swift | 69 +++++++++++++------ .../Select/NCSelectOpen+SelectDelegate.swift | 4 +- .../Settings/E2EE/NCManageE2EEModel.swift | 3 - iOSClient/Share/NCShareNetworking.swift | 14 ++-- iOSClient/UserStatus/NCUserStatusModel.swift | 6 +- .../Utility/NCOperationSaveLivePhoto.swift | 4 +- .../Viewer/NCViewerProviderContextMenu.swift | 4 +- 15 files changed, 90 insertions(+), 53 deletions(-) diff --git a/iOSClient/Assistant/Chat/NCAssistantChatModel.swift b/iOSClient/Assistant/Chat/NCAssistantChatModel.swift index 3cfb1d9a97..08872e7294 100644 --- a/iOSClient/Assistant/Chat/NCAssistantChatModel.swift +++ b/iOSClient/Assistant/Chat/NCAssistantChatModel.swift @@ -4,7 +4,9 @@ import NextcloudKit -@Observable class NCAssistantChatModel { +@MainActor +@Observable +class NCAssistantChatModel { var messages: [AssistantChatMessage] = [] var isSending: Bool = false var isThinking: Bool = false @@ -22,6 +24,9 @@ import NextcloudKit @ObservationIgnored var controller: NCMainTabBarController? @ObservationIgnored private var chatMessageTaskId: Int? + @ObservationIgnored var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } init(controller: NCMainTabBarController?, messages: [AssistantChatMessage] = []) { self.controller = controller diff --git a/iOSClient/Main/NCPickerViewController.swift b/iOSClient/Main/NCPickerViewController.swift index 76a866dbd2..84b3ab536c 100644 --- a/iOSClient/Main/NCPickerViewController.swift +++ b/iOSClient/Main/NCPickerViewController.swift @@ -11,6 +11,7 @@ import SwiftUI // MARK: - Photo Picker +@MainActor class NCPhotosPickerViewController: NSObject { var controller: NCMainTabBarController var maxSelectedAssets = 1 diff --git a/iOSClient/Menu/NCContextMenuMain.swift b/iOSClient/Menu/NCContextMenuMain.swift index 80926cc0e4..93422e96a3 100644 --- a/iOSClient/Menu/NCContextMenuMain.swift +++ b/iOSClient/Menu/NCContextMenuMain.swift @@ -12,6 +12,7 @@ import LucidBanner /// A context menu used in ``NCCollectionViewCommon`` and ``NCMedia`` /// See ``NCCollectionViewCommon/collectionView(_:contextMenuConfigurationForItemAt:point:)``, /// ``NCCollectionViewCommon/openContextMenu(with:button:sender:)``, ``NCMedia/collectionView(_:contextMenuConfigurationForItemAt:point:)`` for usage details. +@MainActor class NCContextMenuMain: NSObject { let utilityFileSystem = NCUtilityFileSystem() let utility = NCUtility() @@ -299,7 +300,7 @@ class NCContextMenuMain: NSObject { title: NSLocalizedString("_livephoto_save_", comment: ""), image: utility.loadImage(named: "livephoto", colors: [NCBrandColor.shared.iconImageColor]) ) { _ in - NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV, controller: self.controller)) + NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV, windowScene: self.windowScene)) } } @@ -473,15 +474,15 @@ class NCContextMenuMain: NSObject { text: "_offline_not_allowed_", errorCode: NCGlobal.shared.errorOfflineNotAllowed) } else { - let results = await showHudBanner(windowScene: self.windowScene, - title: "_delete_in_progress_") + let results = showHudBanner(windowScene: self.windowScene, + title: "_delete_in_progress_") let error = await NCNetworkingE2EEDelete().delete(metadata: metadata) if error == .success { - await completeHudBannerSuccess(token: results.token, banner: results.banner) + completeHudBannerSuccess(token: results.token, banner: results.banner) } else { - await completeHudBannerError(description: error.errorDescription, token: results.token, banner: results.banner) + completeHudBannerError(description: error.errorDescription, token: results.token, banner: results.banner) } await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in diff --git a/iOSClient/Menu/NCContextMenuProfile.swift b/iOSClient/Menu/NCContextMenuProfile.swift index 3b31845956..bc4b64ab47 100644 --- a/iOSClient/Menu/NCContextMenuProfile.swift +++ b/iOSClient/Menu/NCContextMenuProfile.swift @@ -9,6 +9,7 @@ import NextcloudKit /// A context menu for user profile actions (email, talk, etc.) /// See ``NCShare``, ``NCActivity``, ``NCActivityTableViewCell`` for usage details. +@MainActor class NCContextMenuProfile: NSObject { let userId: String let session: NCSession.Session @@ -19,6 +20,10 @@ class NCContextMenuProfile: NSObject { self.viewController.tabBarController as? NCMainTabBarController } + internal var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } + init(userId: String, session: NCSession.Session, viewController: UIViewController) { self.userId = userId self.session = session @@ -180,7 +185,6 @@ class NCContextMenuProfile: NSObject { private func showError(_ errorKey: String) { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, text: errorKey, errorCode: NCGlobal.shared.errorInternalError) diff --git a/iOSClient/Networking/NCNetworking+ServerError.swift b/iOSClient/Networking/NCNetworking+ServerError.swift index 5f24effce0..f64c126173 100644 --- a/iOSClient/Networking/NCNetworking+ServerError.swift +++ b/iOSClient/Networking/NCNetworking+ServerError.swift @@ -71,7 +71,7 @@ extension NCNetworking { if serverInfo.maintenance { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: controller) await showInfoBanner(windowScene: windowScene, title: "_warning_", text: "_maintenance_mode_", diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 2979830d08..50ba5fb80a 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -292,7 +292,7 @@ extension NCNetworking { } else if (error.errorCode == self.global.errorBadRequest || error.errorCode == self.global.errorUnsupportedMediaType) && error.errorDescription.localizedCaseInsensitiveContains("virus") { await uploadCancelFile(metadata: metadata) #if !EXTENSION - let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: metadata.sceneIdentifier) + let windowScene = await SceneManager.shared.getWindow(sceneIdentifier: metadata.sceneIdentifier)?.windowScene await showErrorBanner(windowScene: windowScene, text: "_virus_detect_", errorCode: self.global.errorBadRequest) #endif // Client Diagnostic diff --git a/iOSClient/Networking/NCService.swift b/iOSClient/Networking/NCService.swift index 30ee848ff4..476d376aea 100644 --- a/iOSClient/Networking/NCService.swift +++ b/iOSClient/Networking/NCService.swift @@ -60,7 +60,7 @@ class NCService: NSObject { } switch resultServerStatus.result { case .success(let serverInfo): - let windowScene = SceneManager.shared.getWindowScene(controller: controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: controller) if serverInfo.maintenance { return false } else if serverInfo.productName.lowercased().contains("owncloud") { diff --git a/iOSClient/RichWorkspace/NCRichWorkspaceCommon.swift b/iOSClient/RichWorkspace/NCRichWorkspaceCommon.swift index e27640b7fe..557f2beb60 100644 --- a/iOSClient/RichWorkspace/NCRichWorkspaceCommon.swift +++ b/iOSClient/RichWorkspace/NCRichWorkspaceCommon.swift @@ -11,7 +11,7 @@ class NCRichWorkspaceCommon: NSObject { func createViewerNextcloudText(serverUrl: String, viewController: UIViewController, controller: NCMainTabBarController?, session: NCSession.Session) { if !NextcloudKit.shared.isNetworkReachable() { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, text: "_go_online_", errorCode: NCGlobal.shared.errorOfflineNotAllowed) } return @@ -43,7 +43,7 @@ class NCRichWorkspaceCommon: NSObject { } } else if error != .success { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } @@ -53,7 +53,7 @@ class NCRichWorkspaceCommon: NSObject { func openViewerNextcloudText(serverUrl: String, viewController: UIViewController, controller: NCMainTabBarController?, session: NCSession.Session) { if !NextcloudKit.shared.isNetworkReachable() { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, text: "_go_online_", errorCode: NCGlobal.shared.errorOfflineNotAllowed) } return @@ -87,7 +87,7 @@ class NCRichWorkspaceCommon: NSObject { } } else if error != .success { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index 168d7e57ae..208753cdff 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -556,7 +556,8 @@ extension SceneDelegate: NCAccountRequestDelegate { // MARK: - Scene Manager -final class SceneManager: @unchecked Sendable { +@MainActor +final class SceneManager { static let shared = SceneManager() private var sceneController: [NCMainTabBarController: UIScene] = [:] @@ -589,40 +590,68 @@ final class SceneManager: @unchecked Sendable { } func getWindow(scene: UIScene?) -> UIWindow? { - return (scene as? UIWindowScene)?.keyWindow + guard let windowScene = scene as? UIWindowScene else { return nil } + + return windowScene.keyWindow } + func getWindow(controller: UITabBarController?) -> UIWindow? { guard let controller = controller as? NCMainTabBarController, let scene = sceneController[controller] else { return nil } return getWindow(scene: scene) } - func getWindowScene(controller: UITabBarController?) -> UIWindowScene? { - guard let controller = controller as? NCMainTabBarController, - let scene = sceneController[controller] else { return nil } - return getWindow(scene: scene)?.windowScene + func getWindowScene(controller: UIViewController?) -> UIWindowScene? { + if let windowScene = controller?.viewIfLoaded?.window?.windowScene { + return windowScene + } + + // Fallback: if the controller is a registered NCMainTabBarController. + if let mainTabBarController = controller as? NCMainTabBarController, + let scene = sceneController[mainTabBarController] as? UIWindowScene { + return scene + } + + // Fallback: any foregroundActive scene. + if let active = UIApplication.shared.connectedScenes + .compactMap({ $0 as? UIWindowScene }) + .first(where: { $0.activationState == .foregroundActive }) { + return active + } + + // Last resort: literally the first connected window scene. + return UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .first } func getWindow(sceneIdentifier: String?) -> UIWindow? { - var mainTabBarController: NCMainTabBarController? + // Try exact match via your registry + if let sceneIdentifier, + let controller = sceneController.keys.first(where: { $0.sceneIdentifier == sceneIdentifier }), + let scene = sceneController[controller] { + return getWindow(scene: scene) + } - if let sceneIdentifier { - for controller in sceneController.keys { - if sceneIdentifier == controller.sceneIdentifier { - mainTabBarController = controller - } - } + // Fallback: prefer a foregroundActive window scene + if let active = UIApplication.shared.connectedScenes + .compactMap({ $0 as? UIWindowScene }) + .first(where: { $0.activationState == .foregroundActive }), + let w = active.keyWindow { + return w } - guard let mainTabBarController, - let scene = sceneController[mainTabBarController] else { - return UIApplication.shared.mainAppWindow + + // Last resort: first connected window scene + if let any = UIApplication.shared.connectedScenes + .compactMap({ $0 as? UIWindowScene }) + .first, + let w = any.keyWindow { + return w } - return getWindow(scene: scene) - } - func getWindowScene(sceneIdentifier: String?) -> UIWindowScene? { - return getWindowScene(sceneIdentifier: sceneIdentifier) + // Absolute last resort (if you keep it) + return UIApplication.shared.mainAppWindow } func getSceneIdentifier() -> [String] { diff --git a/iOSClient/Select/NCSelectOpen+SelectDelegate.swift b/iOSClient/Select/NCSelectOpen+SelectDelegate.swift index aab2edfd27..045027f854 100644 --- a/iOSClient/Select/NCSelectOpen+SelectDelegate.swift +++ b/iOSClient/Select/NCSelectOpen+SelectDelegate.swift @@ -13,7 +13,7 @@ final class NCSelectOpen: NCSelectDelegate { } let error = await NCNetworking.shared.setStatusWaitCopy(metadata, destination: destination, overwrite: overwrite) if error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, error: error) } } @@ -25,7 +25,7 @@ final class NCSelectOpen: NCSelectDelegate { } let error = await NCNetworking.shared.setStatusWaitMove(metadata, destination: destination, overwrite: overwrite) if error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, error: error) } } diff --git a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift index 34231ca918..e62079b62d 100644 --- a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift +++ b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift @@ -22,9 +22,6 @@ class NCManageE2EE: NSObject, ObservableObject, ViewOnAppearHandling, NCEndToEnd var capabilities: NKCapabilities.Capabilities { NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() } - var windowScene: UIWindowScene? { - SceneManager.shared.getWindowScene(controller: controller) - } init(controller: NCMainTabBarController?) { super.init() diff --git a/iOSClient/Share/NCShareNetworking.swift b/iOSClient/Share/NCShareNetworking.swift index bb9179126b..3a7b64092a 100644 --- a/iOSClient/Share/NCShareNetworking.swift +++ b/iOSClient/Share/NCShareNetworking.swift @@ -115,7 +115,7 @@ class NCShareNetworking: NSObject { NCActivityIndicator.shared.stop() } Task { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, error: error) } self.delegate?.readShareCompleted() @@ -171,7 +171,7 @@ class NCShareNetworking: NSObject { } } else { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, error: error) } } @@ -203,7 +203,7 @@ class NCShareNetworking: NSObject { } } else { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, error: error) } } @@ -247,7 +247,7 @@ class NCShareNetworking: NSObject { } } else { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, error: error) } self.delegate?.updateShareWithError(idShare: shareable.idShare) @@ -271,7 +271,7 @@ class NCShareNetworking: NSObject { self.delegate?.getSharees(sharees: sharees) } else { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, error: error) } self.delegate?.getSharees(sharees: nil) @@ -300,7 +300,7 @@ class NCShareNetworking: NSObject { self.delegate?.downloadLimitRemoved(by: token) } else { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, error: error) } } @@ -329,7 +329,7 @@ class NCShareNetworking: NSObject { } else { self.delegate?.downloadLimitRemoved(by: token) Task { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, error: error) } } diff --git a/iOSClient/UserStatus/NCUserStatusModel.swift b/iOSClient/UserStatus/NCUserStatusModel.swift index cfd47f106f..2d20af5658 100644 --- a/iOSClient/UserStatus/NCUserStatusModel.swift +++ b/iOSClient/UserStatus/NCUserStatusModel.swift @@ -49,7 +49,7 @@ import NextcloudKit if result.error == .success { selectedStatus = result.status } else { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, error: result.error) } } @@ -66,7 +66,7 @@ import NextcloudKit } if result.error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, error: result.error) } } @@ -82,7 +82,7 @@ import NextcloudKit } if result.error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, error: result.error) } diff --git a/iOSClient/Utility/NCOperationSaveLivePhoto.swift b/iOSClient/Utility/NCOperationSaveLivePhoto.swift index 82e6ca2c63..7ddb3c7cd7 100644 --- a/iOSClient/Utility/NCOperationSaveLivePhoto.swift +++ b/iOSClient/Utility/NCOperationSaveLivePhoto.swift @@ -15,10 +15,10 @@ class NCOperationSaveLivePhoto: ConcurrentOperation, @unchecked Sendable { var tokenBanner: Int? var banner: LucidBanner? - init(metadata: tableMetadata, metadataMOV: tableMetadata, controller: UITabBarController?) { + init(metadata: tableMetadata, metadataMOV: tableMetadata, windowScene: UIWindowScene?) { self.metadata = tableMetadata.init(value: metadata) self.metadataMOV = tableMetadata.init(value: metadataMOV) - windowScene = SceneManager.shared.getWindowScene(controller: controller) + self.windowScene = windowScene } override func start() { diff --git a/iOSClient/Viewer/NCViewerProviderContextMenu.swift b/iOSClient/Viewer/NCViewerProviderContextMenu.swift index ec6f4b1479..4c3866c0f7 100644 --- a/iOSClient/Viewer/NCViewerProviderContextMenu.swift +++ b/iOSClient/Viewer/NCViewerProviderContextMenu.swift @@ -236,7 +236,7 @@ extension NCViewerProviderContextMenu: VLCMediaPlayerDelegate { case .error: NCActivityIndicator.shared.stop() Task { - let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: self.sceneIdentifier) + let windowScene = SceneManager.shared.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene await showErrorBanner(windowScene: windowScene, text: "_error_something_wrong_", errorCode: 0) } print("Played mode: ERROR") @@ -295,7 +295,7 @@ extension NCViewerProviderContextMenu: NCTransferDelegate { error: NKError) { if error != .success { Task { - let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: self.sceneIdentifier) + let windowScene = SceneManager.shared.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } From 73994ab6fe71640ba1abaa9aa3d7f1c7f0b46852 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 09:59:22 +0100 Subject: [PATCH 06/20] clean Signed-off-by: Marino Faggiana --- iOSClient/DeepLink/NCDeepLinkHandler.swift | 8 +++++--- iOSClient/GUI/ViewOnAppear.swift | 1 + iOSClient/Main/NCDragDrop.swift | 6 +++--- iOSClient/Menu/NCContextMenuViewer.swift | 7 ++++++- iOSClient/Settings/E2EE/NCEndToEndInitialize.swift | 2 ++ iOSClient/Settings/E2EE/NCManageE2EEModel.swift | 1 + iOSClient/Settings/Settings/SetupPasscodeView.swift | 2 +- .../NCViewerRichdocument/NCViewerRichDocument.swift | 6 +++--- 8 files changed, 22 insertions(+), 11 deletions(-) diff --git a/iOSClient/DeepLink/NCDeepLinkHandler.swift b/iOSClient/DeepLink/NCDeepLinkHandler.swift index 49dff00367..4b23cd2ed4 100644 --- a/iOSClient/DeepLink/NCDeepLinkHandler.swift +++ b/iOSClient/DeepLink/NCDeepLinkHandler.swift @@ -140,9 +140,11 @@ class NCDeepLinkHandler { controller.selectedIndex = ControllerConstants.moreIndex guard let navigationController = controller.viewControllers?[controller.selectedIndex] as? UINavigationController else { return } - let settingsView = NCSettingsView(model: NCSettingsModel(controller: controller)) - let settingsController = UIHostingController(rootView: settingsView) - navigationController.pushViewController(settingsController, animated: true) + Task { @MainActor in + let settingsView = NCSettingsView(model: NCSettingsModel(controller: controller)) + let settingsController = UIHostingController(rootView: settingsView) + navigationController.pushViewController(settingsController, animated: true) + } } private func navigateToAutoUpload(controller: NCMainTabBarController) { diff --git a/iOSClient/GUI/ViewOnAppear.swift b/iOSClient/GUI/ViewOnAppear.swift index f88f78fc2e..cbc8b910b5 100644 --- a/iOSClient/GUI/ViewOnAppear.swift +++ b/iOSClient/GUI/ViewOnAppear.swift @@ -6,6 +6,7 @@ import Foundation import SwiftUI /// A protocol defining methods to handle view appearance events. +@MainActor protocol ViewOnAppearHandling: ObservableObject { // Triggered when the view appears. func onViewAppear() diff --git a/iOSClient/Main/NCDragDrop.swift b/iOSClient/Main/NCDragDrop.swift index a21bc2f353..d77f2e168d 100644 --- a/iOSClient/Main/NCDragDrop.swift +++ b/iOSClient/Main/NCDragDrop.swift @@ -145,7 +145,7 @@ class NCDragDrop: NSObject { database.addMetadata(metadataForUpload) } catch { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, text: error.localizedDescription, errorCode: NCGlobal.shared.errorInternalError) } return @@ -167,7 +167,7 @@ class NCDragDrop: NSObject { error: .success) } } else { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, error: error) } } @@ -188,7 +188,7 @@ class NCDragDrop: NSObject { error: .success) } } else { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, error: error) } } diff --git a/iOSClient/Menu/NCContextMenuViewer.swift b/iOSClient/Menu/NCContextMenuViewer.swift index 18a9fdbb33..ade80c9e93 100644 --- a/iOSClient/Menu/NCContextMenuViewer.swift +++ b/iOSClient/Menu/NCContextMenuViewer.swift @@ -7,6 +7,7 @@ import NextcloudKit /// A context menu created to be used universally with the different `NCViewer`s. /// See ``NCViewerImage``, ``NCViewerMedia``, ``NCViewerPDF`` for usage details. +@MainActor class NCContextMenuViewer: NSObject { let metadata: tableMetadata let controller: NCMainTabBarController? @@ -15,6 +16,10 @@ class NCContextMenuViewer: NSObject { private let database = NCManageDatabase.shared private let utility = NCUtility() + internal var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } + init(metadata: tableMetadata, controller: NCMainTabBarController?, webView: Bool, sender: Any?) { self.metadata = metadata self.controller = controller @@ -145,7 +150,7 @@ class NCContextMenuViewer: NSObject { title: NSLocalizedString("_livephoto_save_", comment: ""), image: utility.loadImage(named: "livephoto", colors: [NCBrandColor.shared.iconImageColor]) ) { _ in - NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV, controller: self.controller)) + NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV, windowScene: self.windowScene)) } } } diff --git a/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift b/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift index cd421a04d2..f977b32a79 100644 --- a/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift +++ b/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift @@ -5,10 +5,12 @@ import UIKit import NextcloudKit +@MainActor @objc protocol NCEndToEndInitializeDelegate { func endToEndInitializeSuccess(metadata: tableMetadata?) } +@MainActor class NCEndToEndInitialize: NSObject { @objc weak var delegate: NCEndToEndInitializeDelegate? let utilityFileSystem = NCUtilityFileSystem() diff --git a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift index e62079b62d..8c0b065dba 100644 --- a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift +++ b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift @@ -7,6 +7,7 @@ import SwiftUI import NextcloudKit import LocalAuthentication +@MainActor class NCManageE2EE: NSObject, ObservableObject, ViewOnAppearHandling, NCEndToEndInitializeDelegate, TOPasscodeViewControllerDelegate { let endToEndInitialize = NCEndToEndInitialize() var passcodeType = "" diff --git a/iOSClient/Settings/Settings/SetupPasscodeView.swift b/iOSClient/Settings/Settings/SetupPasscodeView.swift index a579358af8..2e64e829e5 100644 --- a/iOSClient/Settings/Settings/SetupPasscodeView.swift +++ b/iOSClient/Settings/Settings/SetupPasscodeView.swift @@ -87,7 +87,7 @@ struct SetupPasscodeView: UIViewControllerRepresentable { } else if passcodeSettingsViewController.failedPasscodeAttemptCount == parent.maxFailedAttempts { passcodeSettingsViewController.dismiss(animated: true) Task { - let windowScene = SceneManager.shared.getWindowScene(controller: parent.controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: parent.controller) await showErrorBanner( windowScene: windowScene, text: "_too_many_failed_passcode_attempts_error_", diff --git a/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift b/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift index e0d8f5d2fc..56c68b1197 100644 --- a/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift +++ b/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift @@ -259,7 +259,7 @@ class NCViewerRichDocument: UIViewController, WKNavigationDelegate, WKScriptMess } } else { Task { - let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: self.sceneIdentifier) + let windowScene = SceneManager.shared.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } @@ -322,7 +322,7 @@ class NCViewerRichDocument: UIViewController, WKNavigationDelegate, WKScriptMess self.webView.evaluateJavaScript(functionJS, completionHandler: { _, _ in }) } else { Task { - let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: self.sceneIdentifier) + let windowScene = SceneManager.shared.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } @@ -346,7 +346,7 @@ class NCViewerRichDocument: UIViewController, WKNavigationDelegate, WKScriptMess self.webView.evaluateJavaScript(functionJS, completionHandler: { _, _ in }) } else { Task { - let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: self.sceneIdentifier) + let windowScene = SceneManager.shared.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } From 7d498257e218c6a62ad6e4a15f1f6d41127eeb9e Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 10:05:09 +0100 Subject: [PATCH 07/20] clean Signed-off-by: Marino Faggiana --- Share/NCShareExtension.swift | 1 - iOSClient/GUI/Lucid Banner/AlertActionBannerView.swift | 1 - .../NCCollectionViewCommon+CollectionViewDelegate.swift | 2 +- iOSClient/Menu/ContextMenuActions.swift | 2 +- iOSClient/Menu/NCContextMenuMain.swift | 2 +- iOSClient/Scan document/NCUploadScanDocument.swift | 2 +- iOSClient/SceneDelegate.swift | 1 - iOSClient/Select/NCSelect.swift | 2 +- iOSClient/Settings/E2EE/NCManageE2EEModel.swift | 3 +++ iOSClient/StatusMessage/NCStatusMessageModel.swift | 1 + 10 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Share/NCShareExtension.swift b/Share/NCShareExtension.swift index f874328041..02f9c35c15 100644 --- a/Share/NCShareExtension.swift +++ b/Share/NCShareExtension.swift @@ -52,7 +52,6 @@ class NCShareExtension: UIViewController { var banner: LucidBanner? var sceneIdentifier: String = UUID().uuidString - // MARK: - View Life Cycle override func viewDidLoad() { diff --git a/iOSClient/GUI/Lucid Banner/AlertActionBannerView.swift b/iOSClient/GUI/Lucid Banner/AlertActionBannerView.swift index 7f36b4b842..315753e128 100644 --- a/iOSClient/GUI/Lucid Banner/AlertActionBannerView.swift +++ b/iOSClient/GUI/Lucid Banner/AlertActionBannerView.swift @@ -27,7 +27,6 @@ func showAlertActionBannerView(lucidBanner: LucidBanner?, swipeToDismiss: true ) - lucidBanner.show(payload: payload, policy: .replace) { _, _ in lucidBanner.dismiss() diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift index a803452e84..dad7fd4d0d 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift @@ -31,7 +31,7 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { func downloadFile() async { var downloadRequest: DownloadRequest? let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - var banner : LucidBanner? + var banner: LucidBanner? var tokenBanner: Int? await MainActor.run { (tokenBanner, banner) = showHudBanner(windowScene: windowScene, diff --git a/iOSClient/Menu/ContextMenuActions.swift b/iOSClient/Menu/ContextMenuActions.swift index 8e56a1e7b4..b730f71e21 100644 --- a/iOSClient/Menu/ContextMenuActions.swift +++ b/iOSClient/Menu/ContextMenuActions.swift @@ -145,7 +145,7 @@ enum ContextMenuActions { Task { let error = await NCNetworking.shared.lockUnlockFile(metadata, shouldLock: !isLocked) if error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, error: error) } completion?() diff --git a/iOSClient/Menu/NCContextMenuMain.swift b/iOSClient/Menu/NCContextMenuMain.swift index 93422e96a3..6f98df0612 100644 --- a/iOSClient/Menu/NCContextMenuMain.swift +++ b/iOSClient/Menu/NCContextMenuMain.swift @@ -579,7 +579,7 @@ class NCContextMenuMain: NSObject { } } - let action = await UIAction( + let action = UIAction( title: item.name, image: iconImage ) { _ in diff --git a/iOSClient/Scan document/NCUploadScanDocument.swift b/iOSClient/Scan document/NCUploadScanDocument.swift index 534fc1f84d..a4b894509e 100644 --- a/iOSClient/Scan document/NCUploadScanDocument.swift +++ b/iOSClient/Scan document/NCUploadScanDocument.swift @@ -92,7 +92,7 @@ class NCUploadScanDocument: ObservableObject { for char in self.password.unicodeScalars { if !char.isASCII { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) + let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, text: "_password_ascii_", errorCode: 0) } return DispatchQueue.main.async { diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index 208753cdff..ffb9996caf 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -595,7 +595,6 @@ final class SceneManager { return windowScene.keyWindow } - func getWindow(controller: UITabBarController?) -> UIWindow? { guard let controller = controller as? NCMainTabBarController, let scene = sceneController[controller] else { return nil } diff --git a/iOSClient/Select/NCSelect.swift b/iOSClient/Select/NCSelect.swift index 2fd087ab2f..3e037170e0 100644 --- a/iOSClient/Select/NCSelect.swift +++ b/iOSClient/Select/NCSelect.swift @@ -232,7 +232,7 @@ class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresent error: NKError) { if error != .success { Task { - let windowScene = SceneManager.shared.getWindowScene(sceneIdentifier: sceneIdentifier) + let windowScene = SceneManager.shared.getWindow(sceneIdentifier: sceneIdentifier)?.windowScene await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } diff --git a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift index 8c0b065dba..1df43e03f6 100644 --- a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift +++ b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift @@ -23,6 +23,9 @@ class NCManageE2EE: NSObject, ObservableObject, ViewOnAppearHandling, NCEndToEnd var capabilities: NKCapabilities.Capabilities { NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() } + var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } init(controller: NCMainTabBarController?) { super.init() diff --git a/iOSClient/StatusMessage/NCStatusMessageModel.swift b/iOSClient/StatusMessage/NCStatusMessageModel.swift index b01ea047b7..87ad0340f4 100644 --- a/iOSClient/StatusMessage/NCStatusMessageModel.swift +++ b/iOSClient/StatusMessage/NCStatusMessageModel.swift @@ -5,6 +5,7 @@ import SwiftUI import NextcloudKit +@MainActor @Observable class NCStatusMessageModel { enum ClearAfter: String, CaseIterable, Identifiable { case dontClear = "_dont_clear_" From 56466f08bd35360d2daea24ec8afd91d5585f542 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 10:13:07 +0100 Subject: [PATCH 08/20] code Signed-off-by: Marino Faggiana --- iOSClient/GUI/Lucid Banner/BannerView.swift | 16 +++++++++++----- iOSClient/GUI/Lucid Banner/HelperBanner.swift | 3 ++- .../GUI/Lucid Banner/UploadBannerView.swift | 9 +++++++-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/iOSClient/GUI/Lucid Banner/BannerView.swift b/iOSClient/GUI/Lucid Banner/BannerView.swift index 3f051a835a..7744d2d991 100644 --- a/iOSClient/GUI/Lucid Banner/BannerView.swift +++ b/iOSClient/GUI/Lucid Banner/BannerView.swift @@ -42,11 +42,12 @@ func showBanner(windowScene: UIWindowScene?, ) let banner = LucidBannerRegistry.shared.banner(for: windowScene) + let coordinator = LucidBannerVariantCoordinator(banner: banner) banner.show( payload: payload, policy: policy) { state in - MessageBannerView(state: state) + MessageBannerView(state: state, coordinator: coordinator) } return banner @@ -73,6 +74,8 @@ func showInfoBanner(windowScene: UIWindowScene?, #endif let banner = LucidBannerRegistry.shared.banner(for: windowScene) + let coordinator = LucidBannerVariantCoordinator(banner: banner) + guard let window = banner.windowScene.windows.first else { return } @@ -96,7 +99,7 @@ func showInfoBanner(windowScene: UIWindowScene?, swipeToDismiss: true, ) banner.show(payload: payload) { state in - MessageBannerView(state: state) + MessageBannerView(state: state, coordinator: coordinator) } } @@ -132,6 +135,8 @@ func showErrorBanner(windowScene: UIWindowScene?, #endif let banner = LucidBannerRegistry.shared.banner(for: windowScene) + let coordinator = LucidBannerVariantCoordinator(banner: banner) + guard let window = banner.windowScene.windows.first else { return } @@ -161,7 +166,7 @@ func showErrorBanner(windowScene: UIWindowScene?, banner.dismiss() } ) { state in - MessageBannerView(state: state) + MessageBannerView(state: state, coordinator: coordinator) } } @@ -210,13 +215,14 @@ func bannerContainsError(errorCode: Int?, afError: AFError? = nil) -> Bool { struct MessageBannerView: View { @ObservedObject var state: LucidBannerState + var coordinator: LucidBannerVariantCoordinator? var body: some View { let showTitle = !(state.payload.title?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) let showSubtitle = !(state.payload.subtitle?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) let showFootnote = !(state.payload.footnote?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) - containerView(state: state, allowMinimizeOnTap: false) { + containerView(state: state, coordinator: coordinator, allowMinimizeOnTap: false) { VStack(spacing: 15) { HStack(alignment: .top, spacing: 10) { Image(systemName: state.payload.systemImage ?? "info.circle") @@ -278,7 +284,7 @@ struct MessageBannerView: View { imageAnimation: .drawOn )) - MessageBannerView(state: state) + MessageBannerView(state: state, coordinator: nil) .padding() } } diff --git a/iOSClient/GUI/Lucid Banner/HelperBanner.swift b/iOSClient/GUI/Lucid Banner/HelperBanner.swift index 878e6b8f32..469fa148a8 100644 --- a/iOSClient/GUI/Lucid Banner/HelperBanner.swift +++ b/iOSClient/GUI/Lucid Banner/HelperBanner.swift @@ -8,6 +8,7 @@ import LucidBanner public extension View { @ViewBuilder func containerView(state: LucidBannerState, + coordinator: LucidBannerVariantCoordinator?, allowMinimizeOnTap: Bool, @ViewBuilder _ content: () -> Content) -> some View { let isError = state.payload.stage == .error @@ -20,7 +21,7 @@ public extension View { .contentShape(Rectangle()) .onTapGesture { guard allowMinimizeOnTap else { return } - //LucidBannerVariantCoordinator.shared.handleTap(state) + coordinator?.handleTap(state) } .frame(maxWidth: .infinity, alignment: .center) diff --git a/iOSClient/GUI/Lucid Banner/UploadBannerView.swift b/iOSClient/GUI/Lucid Banner/UploadBannerView.swift index f01e529b32..6b8725ffd6 100644 --- a/iOSClient/GUI/Lucid Banner/UploadBannerView.swift +++ b/iOSClient/GUI/Lucid Banner/UploadBannerView.swift @@ -14,10 +14,12 @@ func showUploadBanner(windowScene: UIWindowScene?, return (nil, nil) } let banner = LucidBannerRegistry.shared.banner(for: windowScene) + let coordinator = LucidBannerVariantCoordinator(banner: banner) let token = banner.show(payload: payload, policy: .drop) { state in UploadBannerView(state: state, + coordinator: coordinator, allowMinimizeOnTap: allowMinimizeOnTap, onButtonTap: onButtonTap) } @@ -44,12 +46,14 @@ func showUploadBanner(windowScene: UIWindowScene?, struct UploadBannerView: View { @ObservedObject var state: LucidBannerState @State var trigger = true + var coordinator: LucidBannerVariantCoordinator? let onButtonTap: (() -> Void)? let allowMinimizeOnTap: Bool let textColor = Color(.label) - init(state: LucidBannerState, allowMinimizeOnTap: Bool = false, onButtonTap: (() -> Void)? = nil) { + init(state: LucidBannerState, coordinator: LucidBannerVariantCoordinator?, allowMinimizeOnTap: Bool = false, onButtonTap: (() -> Void)? = nil) { self.state = state + self.coordinator = coordinator self.allowMinimizeOnTap = allowMinimizeOnTap self.onButtonTap = onButtonTap } @@ -63,7 +67,7 @@ struct UploadBannerView: View { let isError = (state.payload.stage == .error) let isButton = (state.payload.stage == .button) - containerView(state: state, allowMinimizeOnTap: allowMinimizeOnTap) { + containerView(state: state, coordinator: coordinator, allowMinimizeOnTap: allowMinimizeOnTap) { if state.variant == .alternate { HStack(spacing: 5) { Image(systemName: state.payload.systemImage ?? "arrow.up.circle") @@ -219,6 +223,7 @@ struct UploadBannerView: View { UploadBannerView( state: state, + coordinator: nil, allowMinimizeOnTap: false ) .padding() From b8518dc26c43496f2e72d14316a1b9122ccbe0e7 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 10:25:47 +0100 Subject: [PATCH 09/20] code Signed-off-by: Marino Faggiana --- iOSClient/Settings/E2EE/NCManageE2EEModel.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift index 1df43e03f6..3c4d456899 100644 --- a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift +++ b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift @@ -120,7 +120,7 @@ class NCManageE2EE: NSObject, ObservableObject, ViewOnAppearHandling, NCEndToEnd } } - func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode code: String) -> Bool { + nonisolated func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode code: String) -> Bool { if code == NCPreferences().passcode { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { self.correctPasscode() @@ -131,7 +131,7 @@ class NCManageE2EE: NSObject, ObservableObject, ViewOnAppearHandling, NCEndToEnd } } - func didPerformBiometricValidationRequest(in passcodeViewController: TOPasscodeViewController) { + nonisolated func didPerformBiometricValidationRequest(in passcodeViewController: TOPasscodeViewController) { LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: NCBrandOptions.shared.brand) { success, _ in if success { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { @@ -142,7 +142,9 @@ class NCManageE2EE: NSObject, ObservableObject, ViewOnAppearHandling, NCEndToEnd } } - func didTapCancel(in passcodeViewController: TOPasscodeViewController) { - passcodeViewController.dismiss(animated: true) + nonisolated func didTapCancel(in passcodeViewController: TOPasscodeViewController) { + Task {@MainActor in + passcodeViewController.dismiss(animated: true) + } } } From e32e80f5ffada097d12c8e46ce654eb76a85d0d7 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 10:56:34 +0100 Subject: [PATCH 10/20] code Signed-off-by: Marino Faggiana --- iOSClient/GUI/Lucid Banner/BannerView.swift | 12 ++++++------ iOSClient/GUI/Lucid Banner/UploadBannerView.swift | 7 +++---- iOSClient/Settings/E2EE/NCManageE2EEModel.swift | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/iOSClient/GUI/Lucid Banner/BannerView.swift b/iOSClient/GUI/Lucid Banner/BannerView.swift index 7744d2d991..09609be4b1 100644 --- a/iOSClient/GUI/Lucid Banner/BannerView.swift +++ b/iOSClient/GUI/Lucid Banner/BannerView.swift @@ -42,12 +42,12 @@ func showBanner(windowScene: UIWindowScene?, ) let banner = LucidBannerRegistry.shared.banner(for: windowScene) - let coordinator = LucidBannerVariantCoordinator(banner: banner) + let bannerCoordinator = LucidBannerVariantCoordinator(banner: banner) banner.show( payload: payload, policy: policy) { state in - MessageBannerView(state: state, coordinator: coordinator) + MessageBannerView(state: state, coordinator: bannerCoordinator) } return banner @@ -74,7 +74,7 @@ func showInfoBanner(windowScene: UIWindowScene?, #endif let banner = LucidBannerRegistry.shared.banner(for: windowScene) - let coordinator = LucidBannerVariantCoordinator(banner: banner) + let bannerCoordinator = LucidBannerVariantCoordinator(banner: banner) guard let window = banner.windowScene.windows.first else { return @@ -99,7 +99,7 @@ func showInfoBanner(windowScene: UIWindowScene?, swipeToDismiss: true, ) banner.show(payload: payload) { state in - MessageBannerView(state: state, coordinator: coordinator) + MessageBannerView(state: state, coordinator: bannerCoordinator) } } @@ -135,7 +135,7 @@ func showErrorBanner(windowScene: UIWindowScene?, #endif let banner = LucidBannerRegistry.shared.banner(for: windowScene) - let coordinator = LucidBannerVariantCoordinator(banner: banner) + let bannerCoordinator = LucidBannerVariantCoordinator(banner: banner) guard let window = banner.windowScene.windows.first else { return @@ -166,7 +166,7 @@ func showErrorBanner(windowScene: UIWindowScene?, banner.dismiss() } ) { state in - MessageBannerView(state: state, coordinator: coordinator) + MessageBannerView(state: state, coordinator: bannerCoordinator) } } diff --git a/iOSClient/GUI/Lucid Banner/UploadBannerView.swift b/iOSClient/GUI/Lucid Banner/UploadBannerView.swift index 6b8725ffd6..66efa78b45 100644 --- a/iOSClient/GUI/Lucid Banner/UploadBannerView.swift +++ b/iOSClient/GUI/Lucid Banner/UploadBannerView.swift @@ -14,20 +14,19 @@ func showUploadBanner(windowScene: UIWindowScene?, return (nil, nil) } let banner = LucidBannerRegistry.shared.banner(for: windowScene) - let coordinator = LucidBannerVariantCoordinator(banner: banner) + let bannerCoordinator = LucidBannerVariantCoordinator(banner: banner) let token = banner.show(payload: payload, policy: .drop) { state in UploadBannerView(state: state, - coordinator: coordinator, + coordinator: bannerCoordinator, allowMinimizeOnTap: allowMinimizeOnTap, onButtonTap: onButtonTap) } #if !EXTENSION if allowMinimizeOnTap { - let coordinator = LucidBannerVariantCoordinator(banner: banner) - coordinator.register(token: token) { _ in + bannerCoordinator.register(token: token) { _ in return .init( payloadUpdate: .init( horizontalLayout: .centered(width: 100), diff --git a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift index 3c4d456899..cab4dfe5da 100644 --- a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift +++ b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift @@ -16,7 +16,7 @@ class NCManageE2EE: NSObject, ObservableObject, ViewOnAppearHandling, NCEndToEnd @Published var isEndToEndEnabled: Bool = false @Published var statusOfService: String = NSLocalizedString("_status_in_progress_", comment: "") @Published var navigateBack: Bool = false - /// Get session + var session: NCSession.Session { NCSession.shared.getSession(controller: controller) } From fd5c78c85e38706c7155f4a098b750ce5930314d Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 11:00:02 +0100 Subject: [PATCH 11/20] fix Signed-off-by: Marino Faggiana --- iOSClient/GUI/Lucid Banner/BannerView.swift | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/iOSClient/GUI/Lucid Banner/BannerView.swift b/iOSClient/GUI/Lucid Banner/BannerView.swift index 09609be4b1..f6c363f25a 100644 --- a/iOSClient/GUI/Lucid Banner/BannerView.swift +++ b/iOSClient/GUI/Lucid Banner/BannerView.swift @@ -42,12 +42,11 @@ func showBanner(windowScene: UIWindowScene?, ) let banner = LucidBannerRegistry.shared.banner(for: windowScene) - let bannerCoordinator = LucidBannerVariantCoordinator(banner: banner) banner.show( payload: payload, policy: policy) { state in - MessageBannerView(state: state, coordinator: bannerCoordinator) + MessageBannerView(state: state) } return banner @@ -74,7 +73,6 @@ func showInfoBanner(windowScene: UIWindowScene?, #endif let banner = LucidBannerRegistry.shared.banner(for: windowScene) - let bannerCoordinator = LucidBannerVariantCoordinator(banner: banner) guard let window = banner.windowScene.windows.first else { return @@ -99,7 +97,7 @@ func showInfoBanner(windowScene: UIWindowScene?, swipeToDismiss: true, ) banner.show(payload: payload) { state in - MessageBannerView(state: state, coordinator: bannerCoordinator) + MessageBannerView(state: state) } } @@ -135,7 +133,6 @@ func showErrorBanner(windowScene: UIWindowScene?, #endif let banner = LucidBannerRegistry.shared.banner(for: windowScene) - let bannerCoordinator = LucidBannerVariantCoordinator(banner: banner) guard let window = banner.windowScene.windows.first else { return @@ -166,7 +163,7 @@ func showErrorBanner(windowScene: UIWindowScene?, banner.dismiss() } ) { state in - MessageBannerView(state: state, coordinator: bannerCoordinator) + MessageBannerView(state: state) } } @@ -215,14 +212,13 @@ func bannerContainsError(errorCode: Int?, afError: AFError? = nil) -> Bool { struct MessageBannerView: View { @ObservedObject var state: LucidBannerState - var coordinator: LucidBannerVariantCoordinator? var body: some View { let showTitle = !(state.payload.title?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) let showSubtitle = !(state.payload.subtitle?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) let showFootnote = !(state.payload.footnote?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) - containerView(state: state, coordinator: coordinator, allowMinimizeOnTap: false) { + containerView(state: state, coordinator: nil, allowMinimizeOnTap: false) { VStack(spacing: 15) { HStack(alignment: .top, spacing: 10) { Image(systemName: state.payload.systemImage ?? "info.circle") @@ -284,7 +280,7 @@ struct MessageBannerView: View { imageAnimation: .drawOn )) - MessageBannerView(state: state, coordinator: nil) + MessageBannerView(state: state) .padding() } } From 234cc1159e24094f4d4239b09ed9d259dae0faf4 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 11:23:37 +0100 Subject: [PATCH 12/20] code Signed-off-by: Marino Faggiana --- .../Collection Common/NCCollectionViewCommon.swift | 13 ++++++++----- .../NCSectionFirstHeader.swift | 1 + iOSClient/Media/NCMedia.swift | 2 ++ iOSClient/Menu/NCContextMenuComment.swift | 1 + iOSClient/More/NCMore.swift | 2 ++ iOSClient/Notification/NCNotification.swift | 1 + iOSClient/Scan document/NCScan.swift | 1 - .../Advanced/File Name/NCFileNameModel.swift | 1 + .../Settings/Advanced/NCSettingsAdvancedModel.swift | 1 + iOSClient/Share/NCShare.swift | 1 + iOSClient/Trash/NCTrash.swift | 2 ++ .../NCViewerNextcloudText.swift | 1 + .../NCViewerQuickLook/NCViewerQuickLookView.swift | 8 ++++++-- .../NCViewerRichdocument/NCViewerRichDocument.swift | 2 ++ 14 files changed, 29 insertions(+), 8 deletions(-) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 130b572d11..e44dca89e8 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -94,11 +94,6 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, internal let heightHeaderRecommendations: CGFloat = 160 internal let heightHeaderSection: CGFloat = 30 - @MainActor - internal var session: NCSession.Session { - NCSession.shared.getSession(controller: tabBarController) - } - internal var isLayoutPhoto: Bool { layoutForView?.layout == global.layoutPhotoRatio || layoutForView?.layout == global.layoutPhotoSquare } @@ -124,19 +119,27 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, layoutForView?.layout == global.layoutList ? " - " : "" } + @MainActor + internal var session: NCSession.Session { + NCSession.shared.getSession(controller: tabBarController) + } + @MainActor internal var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController } + @MainActor internal var mainNavigationController: NCMainNavigationController? { self.navigationController as? NCMainNavigationController } + @MainActor internal var sceneIdentifier: String { (self.tabBarController as? NCMainTabBarController)?.sceneIdentifier ?? "" } + @MainActor internal var windowScene: UIWindowScene? { SceneManager.shared.getWindowScene(controller: self.tabBarController as? NCMainTabBarController) } diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index d02695bb03..d655c2368c 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -36,6 +36,7 @@ class NCSectionFirstHeader: UICollectionReusableView, UIGestureRecognizerDelegat private var sceneIdentifier: String = "" #if !EXTENSION + @MainActor internal var controller: NCMainTabBarController? { viewController?.tabBarController as? NCMainTabBarController } diff --git a/iOSClient/Media/NCMedia.swift b/iOSClient/Media/NCMedia.swift index 8e61db41e0..1c189a887c 100644 --- a/iOSClient/Media/NCMedia.swift +++ b/iOSClient/Media/NCMedia.swift @@ -60,6 +60,7 @@ class NCMedia: UIViewController { NCSession.shared.getSession(controller: tabBarController) } + @MainActor var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController } @@ -72,6 +73,7 @@ class NCMedia: UIViewController { return pinchGesture.state == .began || pinchGesture.state == .changed } + @MainActor var sceneIdentifier: String { (self.tabBarController as? NCMainTabBarController)?.sceneIdentifier ?? "" } diff --git a/iOSClient/Menu/NCContextMenuComment.swift b/iOSClient/Menu/NCContextMenuComment.swift index 37a32cdebd..c7a82ed979 100644 --- a/iOSClient/Menu/NCContextMenuComment.swift +++ b/iOSClient/Menu/NCContextMenuComment.swift @@ -7,6 +7,7 @@ import NextcloudKit /// A context menu for comment actions (edit, delete). /// See ``NCActivity`` for usage details. +@MainActor class NCContextMenuComment: NSObject { let tableComments: tableComments let metadata: tableMetadata diff --git a/iOSClient/More/NCMore.swift b/iOSClient/More/NCMore.swift index 2f8914c6c9..129b76d1d5 100644 --- a/iOSClient/More/NCMore.swift +++ b/iOSClient/More/NCMore.swift @@ -59,10 +59,12 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource { NCSession.shared.getSession(controller: tabBarController) } + @MainActor private var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController } + @MainActor var mainNavigationController: NCMainNavigationController? { self.navigationController as? NCMainNavigationController } diff --git a/iOSClient/Notification/NCNotification.swift b/iOSClient/Notification/NCNotification.swift index 501cd3f2a5..225c8cf6f9 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -32,6 +32,7 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { var notifications: [NKNotifications] = [] var session: NCSession.Session! + @MainActor var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController } diff --git a/iOSClient/Scan document/NCScan.swift b/iOSClient/Scan document/NCScan.swift index 7801b50852..a8d5ca567b 100755 --- a/iOSClient/Scan document/NCScan.swift +++ b/iOSClient/Scan document/NCScan.swift @@ -27,7 +27,6 @@ import EasyTipView import SwiftUI class NCScan: UIViewController, NCScanCellCellDelegate { - @IBOutlet weak var collectionViewSource: UICollectionView! @IBOutlet weak var collectionViewDestination: UICollectionView! @IBOutlet weak var cancel: UIBarButtonItem! diff --git a/iOSClient/Settings/Advanced/File Name/NCFileNameModel.swift b/iOSClient/Settings/Advanced/File Name/NCFileNameModel.swift index d9c071c740..63939a5d31 100644 --- a/iOSClient/Settings/Advanced/File Name/NCFileNameModel.swift +++ b/iOSClient/Settings/Advanced/File Name/NCFileNameModel.swift @@ -25,6 +25,7 @@ class NCFileNameModel: ObservableObject, ViewOnAppearHandling { // Root View Controller @Published var controller: NCMainTabBarController? // Get session + @MainActor var session: NCSession.Session { NCSession.shared.getSession(controller: controller) } diff --git a/iOSClient/Settings/Advanced/NCSettingsAdvancedModel.swift b/iOSClient/Settings/Advanced/NCSettingsAdvancedModel.swift index 4d79b432c3..1a862aa596 100644 --- a/iOSClient/Settings/Advanced/NCSettingsAdvancedModel.swift +++ b/iOSClient/Settings/Advanced/NCSettingsAdvancedModel.swift @@ -34,6 +34,7 @@ class NCSettingsAdvancedModel: ObservableObject, ViewOnAppearHandling { // Root View Controller @Published var controller: NCMainTabBarController? // Get session + @MainActor var session: NCSession.Session { NCSession.shared.getSession(controller: controller) } diff --git a/iOSClient/Share/NCShare.swift b/iOSClient/Share/NCShare.swift index ab2f21c425..8b0f109127 100644 --- a/iOSClient/Share/NCShare.swift +++ b/iOSClient/Share/NCShare.swift @@ -58,6 +58,7 @@ class NCShare: UIViewController, NCSharePagingContent { return ((metadata.sharePermissionsCollaborationServices & NKShare.Permission.share.rawValue) != 0) } + @MainActor var session: NCSession.Session { NCSession.shared.getSession(account: metadata.account) } diff --git a/iOSClient/Trash/NCTrash.swift b/iOSClient/Trash/NCTrash.swift index ea7bd3489d..e1e4cca080 100644 --- a/iOSClient/Trash/NCTrash.swift +++ b/iOSClient/Trash/NCTrash.swift @@ -52,10 +52,12 @@ class NCTrash: UIViewController, NCTrashListCellDelegate, NCTrashGridCellDelegat NCSession.shared.getSession(controller: tabBarController) } + @MainActor var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController } + @MainActor var mainNavigationController: NCMainNavigationController? { self.navigationController as? NCMainNavigationController } diff --git a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift b/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift index 70fa832d15..5498ae3fc6 100644 --- a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift +++ b/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift @@ -16,6 +16,7 @@ class NCViewerNextcloudText: UIViewController, WKNavigationDelegate, WKScriptMes let utility = NCUtility() var items: [UIBarButtonItem] = [] + @MainActor var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController } diff --git a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLookView.swift b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLookView.swift index 608800321f..e2a3669ef7 100644 --- a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLookView.swift +++ b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLookView.swift @@ -56,7 +56,9 @@ struct NCViewerQuickLookView: UIViewControllerRepresentable { super.init() NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in - parent.model.stopTimer() + Task { + await parent.model.stopTimer() + } } NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in @@ -64,7 +66,9 @@ struct NCViewerQuickLookView: UIViewControllerRepresentable { let navigationItem = self.viewController?.navigationItem else { return } - parent.model.startTimer(navigationItem: navigationItem) + Task { + await parent.model.startTimer(navigationItem: navigationItem) + } } } diff --git a/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift b/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift index 56c68b1197..00504f8b20 100644 --- a/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift +++ b/iOSClient/Viewer/NCViewerRichdocument/NCViewerRichDocument.swift @@ -17,10 +17,12 @@ class NCViewerRichDocument: UIViewController, WKNavigationDelegate, WKScriptMess var metadata: tableMetadata = tableMetadata() var imageIcon: UIImage? + @MainActor var session: NCSession.Session { NCSession.shared.getSession(account: metadata.account) } + @MainActor var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController } From 8ac7dbbfebd5265434bc5f5754962024f1b76159 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 11:56:16 +0100 Subject: [PATCH 13/20] fix dependency Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 5996ce3c68..b3f2f76af5 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -234,6 +234,8 @@ F711A4E92AF9327600095DD8 /* UIImage+animatedGIF.m in Sources */ = {isa = PBXBuildFile; fileRef = F713FEFF2472764100214AF6 /* UIImage+animatedGIF.m */; }; F711A4EB2AF9327D00095DD8 /* UIImage+animatedGIF.m in Sources */ = {isa = PBXBuildFile; fileRef = F713FEFF2472764100214AF6 /* UIImage+animatedGIF.m */; }; F711D63128F44801003F43C8 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9739428F17131002C43E2 /* IntentHandler.swift */; }; + F712217E2F51AF9500ACEB08 /* LucidBanner in Frameworks */ = {isa = PBXBuildFile; productRef = F712217D2F51AF9500ACEB08 /* LucidBanner */; }; + F71221802F51AF9F00ACEB08 /* LucidBanner in Frameworks */ = {isa = PBXBuildFile; productRef = F712217F2F51AF9F00ACEB08 /* LucidBanner */; }; F7132C722D085AD200B42D6A /* NCTermOfServiceModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7132C6B2D085AD200B42D6A /* NCTermOfServiceModel.swift */; }; F7132C732D085AD200B42D6A /* NCTermOfServiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7132C6C2D085AD200B42D6A /* NCTermOfServiceView.swift */; }; F713FF002472764100214AF6 /* UIImage+animatedGIF.m in Sources */ = {isa = PBXBuildFile; fileRef = F713FEFF2472764100214AF6 /* UIImage+animatedGIF.m */; }; @@ -1874,6 +1876,7 @@ files = ( F7490E8B29882CE4009DCE94 /* NextcloudKit in Frameworks */, F7490E7229882BB4009DCE94 /* RealmSwift in Frameworks */, + F71221802F51AF9F00ACEB08 /* LucidBanner in Frameworks */, F760DE0D2AE66EDF0027D78A /* KeychainAccess in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1914,6 +1917,7 @@ files = ( F760DE0B2AE66ED80027D78A /* KeychainAccess in Frameworks */, F710FC84277B7D3500AA9FBF /* RealmSwift in Frameworks */, + F712217E2F51AF9500ACEB08 /* LucidBanner in Frameworks */, F72AD71328C24BCC006CB92D /* NextcloudKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3493,6 +3497,7 @@ F7490E7129882BB4009DCE94 /* RealmSwift */, F7490E8A29882CE4009DCE94 /* NextcloudKit */, F760DE0C2AE66EDF0027D78A /* KeychainAccess */, + F712217F2F51AF9F00ACEB08 /* LucidBanner */, ); productName = "File Provider Extension UI"; productReference = F70716E32987F81500E72C1D /* File Provider Extension UI.appex */; @@ -3572,6 +3577,7 @@ F710FC83277B7D3500AA9FBF /* RealmSwift */, F72AD71228C24BCC006CB92D /* NextcloudKit */, F760DE0A2AE66ED80027D78A /* KeychainAccess */, + F712217D2F51AF9500ACEB08 /* LucidBanner */, ); productName = "File Provider Extension"; productReference = F771E3D020E2392D00AFB62D /* File Provider Extension.appex */; @@ -6334,6 +6340,16 @@ package = F710FC78277B7CFF00AA9FBF /* XCRemoteSwiftPackageReference "realm-swift" */; productName = RealmSwift; }; + F712217D2F51AF9500ACEB08 /* LucidBanner */ = { + isa = XCSwiftPackageProductDependency; + package = F70557B52ED44E2700135623 /* XCRemoteSwiftPackageReference "LucidBanner" */; + productName = LucidBanner; + }; + F712217F2F51AF9F00ACEB08 /* LucidBanner */ = { + isa = XCSwiftPackageProductDependency; + package = F70557B52ED44E2700135623 /* XCRemoteSwiftPackageReference "LucidBanner" */; + productName = LucidBanner; + }; F7160A7C2BE931DE0034DCB3 /* RealmSwift */ = { isa = XCSwiftPackageProductDependency; package = F710FC78277B7CFF00AA9FBF /* XCRemoteSwiftPackageReference "realm-swift" */; From 1d0d027e39f2a71a99dcfcb50a4be5e610ed8fff Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 12:23:40 +0100 Subject: [PATCH 14/20] clean Signed-off-by: Marino Faggiana --- iOSClient/Activity/NCActivity.swift | 15 +++++++--- .../Activity/NCActivityTableViewCell.swift | 5 ++-- .../Assistant/Chat/NCAssistantChatModel.swift | 3 -- iOSClient/Files/NCFiles.swift | 1 - ...ionViewCommon+CollectionViewDelegate.swift | 2 -- .../NCCollectionViewCommon+Search.swift | 4 --- ...ctionViewCommon+SelectTabBarDelegate.swift | 3 +- iOSClient/Main/NCPickerViewController.swift | 1 + iOSClient/Menu/NCContextMenuComment.swift | 10 ++++--- iOSClient/Menu/NCContextMenuMain.swift | 29 ++++++------------- iOSClient/Menu/NCContextMenuPlus.swift | 12 ++++---- iOSClient/Menu/NCContextMenuShare.swift | 11 ++++--- iOSClient/Notification/NCNotification.swift | 11 ++++--- .../AutoUpload/NCAutoUploadModel.swift | 7 +++-- .../Settings/E2EE/NCEndToEndInitialize.swift | 2 ++ .../Settings/E2EE/NCManageE2EEModel.swift | 2 ++ iOSClient/Share/NCShareNetworking.swift | 23 +++++++-------- iOSClient/Trash/NCTrash+Networking.swift | 6 ++-- iOSClient/Trash/NCTrash.swift | 5 ++++ iOSClient/UserStatus/NCUserStatusModel.swift | 7 +++-- .../Viewer/NCViewerMedia/NCViewerMedia.swift | 7 +++-- 21 files changed, 88 insertions(+), 78 deletions(-) diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index ec4657c765..10df054eb5 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -44,6 +44,11 @@ class NCActivity: UIViewController, NCSharePagingContent { } } + @MainActor + internal var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: self.tabBarController as? NCMainTabBarController) + } + // MARK: - View Life Cycle override func viewDidLoad() { @@ -83,8 +88,9 @@ class NCActivity: UIViewController, NCSharePagingContent { self.loadComments() } else { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: self.tabBarController) - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, + text: error.errorDescription, + errorCode: error.errorCode) } } } @@ -425,8 +431,9 @@ extension NCActivity { self.database.addComments(comments, account: metadata.account, objectId: metadata.fileId) } else if error.errorCode != NCGlobal.shared.errorResourceNotFound { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: self.tabBarController) - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, + text: error.errorDescription, + errorCode: error.errorCode) } } diff --git a/iOSClient/Activity/NCActivityTableViewCell.swift b/iOSClient/Activity/NCActivityTableViewCell.swift index 89b55281dc..3e3ec8737f 100644 --- a/iOSClient/Activity/NCActivityTableViewCell.swift +++ b/iOSClient/Activity/NCActivityTableViewCell.swift @@ -82,8 +82,9 @@ extension NCActivityTableViewCell: UICollectionViewDelegate { (responder as? UIViewController)!.navigationController?.pushViewController(viewController, animated: true) } else { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: viewController.controller) - await showErrorBanner(windowScene: windowScene, text: "_trash_file_not_found_", errorCode: NCGlobal.shared.errorInternalError) + await showErrorBanner(windowScene: viewController.windowScene, + text: "_trash_file_not_found_", + errorCode: NCGlobal.shared.errorInternalError) } } } diff --git a/iOSClient/Assistant/Chat/NCAssistantChatModel.swift b/iOSClient/Assistant/Chat/NCAssistantChatModel.swift index 08872e7294..4722af24da 100644 --- a/iOSClient/Assistant/Chat/NCAssistantChatModel.swift +++ b/iOSClient/Assistant/Chat/NCAssistantChatModel.swift @@ -101,7 +101,6 @@ class NCAssistantChatModel { if result.error == .success { messages = result.chatMessages ?? [] } else { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, title: "_error_", text: "_assistant_error_load_messages_", errorCode: result.error.errorCode) } } @@ -113,7 +112,6 @@ class NCAssistantChatModel { if result.error != .success { stopPolling() - let windowScene = SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, title: "_error_", text: "_assistant_error_generate_response_", errorCode: result.error.errorCode) return } @@ -141,7 +139,6 @@ class NCAssistantChatModel { await generateChatSession() startPollingForResponse() } else { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) await showErrorBanner(windowScene: windowScene, title: "_error_", text: "_assistant_error_send_message_", errorCode: 20) } diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index 11305e578c..eb81f3a2c3 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -230,7 +230,6 @@ class NCFiles: NCCollectionViewCommon { } private func networkReadFolderAsync(serverUrl: String, forced: Bool) async -> (metadatas: [tableMetadata]?, error: NKError, reloadRequired: Bool) { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) var reloadRequired: Bool = false let resultsReadFile = await NCNetworking.shared.readFileAsync(serverUrlFileName: serverUrl, account: session.account) { task in Task { diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift index dad7fd4d0d..51dbb3c603 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift @@ -12,7 +12,6 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { @MainActor func didSelectMetadata(_ metadata: tableMetadata, withOcIds: Bool) async { let capabilities = await NKCapabilities.shared.getCapabilities(for: session.account) - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) if metadata.e2eEncrypted { if capabilities.e2EEEnabled { @@ -30,7 +29,6 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { func downloadFile() async { var downloadRequest: DownloadRequest? - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) var banner: LucidBanner? var tokenBanner: Int? await MainActor.run { diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift index 487757287d..8f1c4316a3 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift @@ -75,7 +75,6 @@ extension NCCollectionViewCommon { account: self.session.account ) } else { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) @@ -118,7 +117,6 @@ extension NCCollectionViewCommon { } if results.error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode, @@ -152,7 +150,6 @@ extension NCCollectionViewCommon { ) if results.error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode, @@ -212,7 +209,6 @@ extension NCCollectionViewCommon { ) if results.error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode, diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift index 103213aa05..1dbfd43dde 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift @@ -43,8 +43,7 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { if !metadatasPlain.isEmpty { let error = await self.networking.setStatusWaitDelete(metadatas: metadatasPlain) if error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } diff --git a/iOSClient/Main/NCPickerViewController.swift b/iOSClient/Main/NCPickerViewController.swift index 84b3ab536c..d788763971 100644 --- a/iOSClient/Main/NCPickerViewController.swift +++ b/iOSClient/Main/NCPickerViewController.swift @@ -17,6 +17,7 @@ class NCPhotosPickerViewController: NSObject { var maxSelectedAssets = 1 var singleSelectedMode = false let global = NCGlobal.shared + var windowScene: UIWindowScene? { SceneManager.shared.getWindowScene(controller: controller) } diff --git a/iOSClient/Menu/NCContextMenuComment.swift b/iOSClient/Menu/NCContextMenuComment.swift index c7a82ed979..41f002f692 100644 --- a/iOSClient/Menu/NCContextMenuComment.swift +++ b/iOSClient/Menu/NCContextMenuComment.swift @@ -18,6 +18,10 @@ class NCContextMenuComment: NSObject { self.viewController?.tabBarController as? NCMainTabBarController } + internal var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: self.viewController?.tabBarController as? NCMainTabBarController) + } + init(tableComments: tableComments, metadata: tableMetadata, viewController: UIViewController?) { self.tableComments = tableComments self.metadata = metadata @@ -63,8 +67,7 @@ class NCContextMenuComment: NSObject { NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) } else { Task { @MainActor in - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } @@ -98,8 +101,7 @@ class NCContextMenuComment: NSObject { (self.viewController as? NCActivity)?.loadComments() } else { Task { @MainActor in - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } diff --git a/iOSClient/Menu/NCContextMenuMain.swift b/iOSClient/Menu/NCContextMenuMain.swift index 6f98df0612..b49e3f8d4c 100644 --- a/iOSClient/Menu/NCContextMenuMain.swift +++ b/iOSClient/Menu/NCContextMenuMain.swift @@ -244,8 +244,7 @@ class NCContextMenuMain: NSObject { userId: metadata.userId ) if error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } @@ -283,8 +282,7 @@ class NCContextMenuMain: NSObject { await NCManageDatabase.shared.setMetadataEncryptedAsync(ocId: metadata.ocId, encrypted: false) await (self.viewController as? NCCollectionViewCommon)?.reloadDataSource() } else { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, + await showErrorBanner(windowScene: self.windowScene, title: "_e2e_error_", text: results.error.errorDescription, errorCode: results.error.errorCode) @@ -360,8 +358,7 @@ class NCContextMenuMain: NSObject { fileNameNew ) ) != nil { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, + await showErrorBanner(windowScene: self.windowScene, text: "_rename_already_exists_", errorCode: 0) return @@ -369,8 +366,7 @@ class NCContextMenuMain: NSObject { let error = await NCNetworking.shared.setStatusWaitRename(metadata, fileNameNew: fileNameNew) if error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } } @@ -469,8 +465,7 @@ class NCContextMenuMain: NSObject { Task { if metadata.isDirectoryE2EE { if NCNetworking.shared.isOffline { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, + await showErrorBanner(windowScene: self.windowScene, text: "_offline_not_allowed_", errorCode: NCGlobal.shared.errorOfflineNotAllowed) } else { @@ -492,8 +487,7 @@ class NCContextMenuMain: NSObject { } else { let error = await NCNetworking.shared.setStatusWaitDelete(metadatas: [metadata]) if error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } } @@ -514,11 +508,7 @@ class NCContextMenuMain: NSObject { var token: Int? var banner: LucidBanner? if metadata.isDirectory { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - (token, banner) = showHudBanner( - windowScene: windowScene, - title: "_delete_in_progress_" - ) + (token, banner) = showHudBanner(windowScene: self.windowScene, title: "_delete_in_progress_") } await NCNetworking.shared.deleteCache(metadata, progress: { progress in @@ -584,7 +574,6 @@ class NCContextMenuMain: NSObject { image: iconImage ) { _ in Task { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) let results = await NextcloudKit.shared.sendRequestAsync( account: metadata.account, fileId: metadata.fileId, @@ -594,12 +583,12 @@ class NCContextMenuMain: NSObject { params: item.params ) if results.error != .success { - await showErrorBanner(windowScene: windowScene, + await showErrorBanner(windowScene: self.windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) } else { if let tooltip = results.uiResponse?.ocs.data.tooltip { - await showInfoBanner(windowScene: windowScene, text: tooltip) + await showInfoBanner(windowScene: self.windowScene, text: tooltip) } else { let baseURL = metadata.urlBase diff --git a/iOSClient/Menu/NCContextMenuPlus.swift b/iOSClient/Menu/NCContextMenuPlus.swift index 9b6ae683e6..2d1cdefdbb 100644 --- a/iOSClient/Menu/NCContextMenuPlus.swift +++ b/iOSClient/Menu/NCContextMenuPlus.swift @@ -6,16 +6,20 @@ import Foundation import UIKit import NextcloudKit +@MainActor class NCContextMenuPlus: NSObject { let menuToolbar: UIToolbar? let controller: NCMainTabBarController? + internal var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } + init(menuToolbar: UIToolbar?, controller: NCMainTabBarController?) { self.menuToolbar = menuToolbar self.controller = controller } - @MainActor func create(session: NCSession.Session) async { guard let controller, let menuToolbar else { return @@ -90,8 +94,7 @@ class NCContextMenuPlus: NSObject { capabilities: capabilities) { error in if error != .success { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) - await showErrorBanner(windowScene: windowScene, + await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) } @@ -117,8 +120,7 @@ class NCContextMenuPlus: NSObject { capabilities: capabilities) { error in if error != .success { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) - await showErrorBanner(windowScene: windowScene, + await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) } diff --git a/iOSClient/Menu/NCContextMenuShare.swift b/iOSClient/Menu/NCContextMenuShare.swift index 1c1ee434e0..7699063069 100644 --- a/iOSClient/Menu/NCContextMenuShare.swift +++ b/iOSClient/Menu/NCContextMenuShare.swift @@ -8,6 +8,7 @@ import NextcloudKit /// A context menu for share actions (details, unshare, permissions). /// See ``NCShare`` for usage details. +@MainActor class NCContextMenuShare: NSObject { let share: tableShare let isDirectory: Bool @@ -17,6 +18,10 @@ class NCContextMenuShare: NSObject { let shareController: NCShare let controller: NCMainTabBarController? + internal var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } + init(share: tableShare, isDirectory: Bool, canReshare: Bool, shareController: NCShare, controller: NCMainTabBarController?) { self.share = share self.isDirectory = isDirectory @@ -155,8 +160,7 @@ class NCContextMenuShare: NSObject { metadata.e2eEncrypted && NCGlobal.shared.isE2eeVersion2(capabilities.e2EEApiVersion) { if await NCNetworkingE2EE().isInUpload(account: metadata.account, serverUrl: metadata.serverUrlFileName) { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) - await showErrorBanner(windowScene: windowScene, + await showErrorBanner(windowScene: self.windowScene, text: "_e2e_in_upload_", errorCode: NCGlobal.shared.errorE2EEUploadInProgress) } @@ -165,8 +169,7 @@ class NCContextMenuShare: NSObject { let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: metadata.serverUrlFileName, addUserId: nil, removeUserId: share.shareWith, account: metadata.account) if error != .success { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) - await showErrorBanner(windowScene: windowScene, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } return } diff --git a/iOSClient/Notification/NCNotification.swift b/iOSClient/Notification/NCNotification.swift index 225c8cf6f9..6669eb4977 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -37,6 +37,11 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { self.tabBarController as? NCMainTabBarController } + @MainActor + internal var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: self.tabBarController as? NCMainTabBarController) + } + // MARK: - View Life Cycle override func viewDidLoad() { @@ -262,8 +267,7 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { self.tableView.reloadData() } else if error != .success { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) } } else { print("[Error] The user has been changed during networking process.") @@ -310,8 +314,7 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { } } else if error != .success { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) } } else { print("[Error] The user has been changed during networking process.") diff --git a/iOSClient/Settings/AutoUpload/NCAutoUploadModel.swift b/iOSClient/Settings/AutoUpload/NCAutoUploadModel.swift index 1430fb6846..0bdabce6cb 100644 --- a/iOSClient/Settings/AutoUpload/NCAutoUploadModel.swift +++ b/iOSClient/Settings/AutoUpload/NCAutoUploadModel.swift @@ -57,6 +57,10 @@ class NCAutoUploadModel: ObservableObject, ViewOnAppearHandling { NCSession.shared.getSession(controller: controller) } + var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } + /// Initialization code to set up the ViewModel with the active account init(controller: NCMainTabBarController?) { self.controller = controller @@ -93,8 +97,7 @@ class NCAutoUploadModel: ObservableObject, ViewOnAppearHandling { if value, UIApplication.shared.backgroundRefreshStatus != .available { Task { - let windowScene = SceneManager.shared.getWindowScene(controller: controller) - await showInfoBanner(windowScene: windowScene, + await showInfoBanner(windowScene: self.windowScene, text: "_access_background_app_refresh_denied_") } } diff --git a/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift b/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift index f977b32a79..8ee4c7cb4f 100644 --- a/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift +++ b/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift @@ -17,9 +17,11 @@ class NCEndToEndInitialize: NSObject { var extractedPublicKey: String? var controller: NCMainTabBarController? var metadata: tableMetadata? + var session: NCSession.Session { NCSession.shared.getSession(controller: controller) } + var windowScene: UIWindowScene? { SceneManager.shared.getWindowScene(controller: controller) } diff --git a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift index cab4dfe5da..e264ad7c09 100644 --- a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift +++ b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift @@ -20,9 +20,11 @@ class NCManageE2EE: NSObject, ObservableObject, ViewOnAppearHandling, NCEndToEnd var session: NCSession.Session { NCSession.shared.getSession(controller: controller) } + var capabilities: NKCapabilities.Capabilities { NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() } + var windowScene: UIWindowScene? { SceneManager.shared.getWindowScene(controller: controller) } diff --git a/iOSClient/Share/NCShareNetworking.swift b/iOSClient/Share/NCShareNetworking.swift index 3a7b64092a..10db2b8eb1 100644 --- a/iOSClient/Share/NCShareNetworking.swift +++ b/iOSClient/Share/NCShareNetworking.swift @@ -32,6 +32,11 @@ class NCShareNetworking: NSObject { let session: NCSession.Session let controller: NCMainTabBarController? + @MainActor + internal var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } + init(metadata: tableMetadata, view: UIView, delegate: NCShareNetworkingDelegate?, @@ -115,8 +120,7 @@ class NCShareNetworking: NSObject { NCActivityIndicator.shared.stop() } Task { - let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } self.delegate?.readShareCompleted() } @@ -171,8 +175,7 @@ class NCShareNetworking: NSObject { } } else { Task { - let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } @@ -203,8 +206,7 @@ class NCShareNetworking: NSObject { } } else { Task { - let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } } @@ -247,8 +249,7 @@ class NCShareNetworking: NSObject { } } else { Task { - let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } self.delegate?.updateShareWithError(idShare: shareable.idShare) } @@ -300,8 +301,7 @@ class NCShareNetworking: NSObject { self.delegate?.downloadLimitRemoved(by: token) } else { Task { - let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } } @@ -329,8 +329,7 @@ class NCShareNetworking: NSObject { } else { self.delegate?.downloadLimitRemoved(by: token) Task { - let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } } diff --git a/iOSClient/Trash/NCTrash+Networking.swift b/iOSClient/Trash/NCTrash+Networking.swift index b46e89730f..e8bc6f1485 100644 --- a/iOSClient/Trash/NCTrash+Networking.swift +++ b/iOSClient/Trash/NCTrash+Networking.swift @@ -86,8 +86,7 @@ extension NCTrash { } if results.error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) } await self.database.deleteTrashAsync(fileId: nil, account: session.account) await self.reloadDataSource() @@ -108,8 +107,7 @@ extension NCTrash { } } if results.error != .success { - let windowScene = SceneManager.shared.getWindowScene(controller: self.controller) - await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) } await self.database.deleteTrashAsync(fileId: fileId, account: session.account) await self.reloadDataSource() diff --git a/iOSClient/Trash/NCTrash.swift b/iOSClient/Trash/NCTrash.swift index e1e4cca080..fef2903036 100644 --- a/iOSClient/Trash/NCTrash.swift +++ b/iOSClient/Trash/NCTrash.swift @@ -57,6 +57,11 @@ class NCTrash: UIViewController, NCTrashListCellDelegate, NCTrashGridCellDelegat self.tabBarController as? NCMainTabBarController } + @MainActor + internal var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: self.tabBarController as? NCMainTabBarController) + } + @MainActor var mainNavigationController: NCMainNavigationController? { self.navigationController as? NCMainNavigationController diff --git a/iOSClient/UserStatus/NCUserStatusModel.swift b/iOSClient/UserStatus/NCUserStatusModel.swift index 2d20af5658..a24b1d09f5 100644 --- a/iOSClient/UserStatus/NCUserStatusModel.swift +++ b/iOSClient/UserStatus/NCUserStatusModel.swift @@ -4,6 +4,7 @@ import NextcloudKit +@MainActor @Observable class NCUserStatusModel { struct UserStatus: Hashable { let name: String @@ -23,6 +24,9 @@ import NextcloudKit @ObservationIgnored let account: String @ObservationIgnored let controller: NCMainTabBarController? + @ObservationIgnored var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } init(account: String, controller: NCMainTabBarController?) { self.account = account @@ -49,7 +53,6 @@ import NextcloudKit if result.error == .success { selectedStatus = result.status } else { - let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, error: result.error) } } @@ -66,7 +69,6 @@ import NextcloudKit } if result.error != .success { - let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, error: result.error) } } @@ -82,7 +84,6 @@ import NextcloudKit } if result.error != .success { - let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) await showErrorBanner(windowScene: windowScene, error: result.error) } diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift index e1cc3b130f..9bd07b7fb4 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift @@ -56,6 +56,10 @@ class NCViewerMedia: UIViewController { (self.tabBarController as? NCMainTabBarController)?.sceneIdentifier ?? "" } + internal var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: self.tabBarController as? NCMainTabBarController) + } + // MARK: - View Life Cycle required init?(coder aDecoder: NSCoder) { @@ -157,9 +161,8 @@ class NCViewerMedia: UIViewController { selector: "") else { return } - let windowScene = SceneManager.shared.getWindowScene(controller: self.tabBarController) var downloadRequest: DownloadRequest? - let (token, banner) = showHudBanner(windowScene: windowScene, + let (token, banner) = showHudBanner(windowScene: self.windowScene, title: "_download_in_progress_", stage: .button) { if let request = downloadRequest { From 1bb400493b073dd63647e3c8798a7748d738a6c4 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 12:26:47 +0100 Subject: [PATCH 15/20] 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 b3f2f76af5..60b07a66a6 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -5725,7 +5725,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; @@ -5791,7 +5791,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 eb49bb99e74922835bad8822f78ea99deb252bee Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 13:51:08 +0100 Subject: [PATCH 16/20] fix Signed-off-by: Marino Faggiana --- iOSClient/Files/NCFiles.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index eb81f3a2c3..9dab7eb759 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -347,7 +347,7 @@ class NCFiles: NCCollectionViewCommon { } else { // Client Diagnostic await self.database.addDiagnosticAsync(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors) - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, text: errorDecodeMetadata.errorDescription, errorCode: errorDecodeMetadata.errorCode) } return (metadatas, error, reloadRequired) From 745bc2a7c5bdc691aa4861a41c0f6568e850a09b Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 14:53:44 +0100 Subject: [PATCH 17/20] cleaning Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 12 ++ iOSClient/GUI/Lucid Banner/BannerView.swift | 181 ++---------------- .../GUI/Lucid Banner/ErrorBannerView.swift | 154 +++++++++++++++ iOSClient/GUI/Lucid Banner/HelperBanner.swift | 39 ++++ .../GUI/Lucid Banner/InfoBannerView.swift | 142 ++++++++++++++ 5 files changed, 359 insertions(+), 169 deletions(-) create mode 100644 iOSClient/GUI/Lucid Banner/ErrorBannerView.swift create mode 100644 iOSClient/GUI/Lucid Banner/InfoBannerView.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 60b07a66a6..07de58511b 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -396,6 +396,10 @@ F74B6D972A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */ = {isa = PBXBuildFile; fileRef = F74B6D942A7E239A00F03C5F /* NCManageDatabase+Chunk.swift */; }; F74B6D982A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */ = {isa = PBXBuildFile; fileRef = F74B6D942A7E239A00F03C5F /* NCManageDatabase+Chunk.swift */; }; F74B6D9B2A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */ = {isa = PBXBuildFile; fileRef = F74B6D942A7E239A00F03C5F /* NCManageDatabase+Chunk.swift */; }; + F74B91E52F51D4170050813D /* InfoBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F74B91E42F51D4100050813D /* InfoBannerView.swift */; }; + F74B91E62F51D4170050813D /* InfoBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F74B91E42F51D4100050813D /* InfoBannerView.swift */; }; + F74B91E82F51D45A0050813D /* ErrorBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F74B91E72F51D4510050813D /* ErrorBannerView.swift */; }; + F74B91E92F51D45A0050813D /* ErrorBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F74B91E72F51D4510050813D /* ErrorBannerView.swift */; }; F74BAE172C7E2F4E0028D4FA /* FileProviderDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76673EC22C901F5007ED366 /* FileProviderDomain.swift */; }; F74C0436253F1CDC009762AB /* NCShares.swift in Sources */ = {isa = PBXBuildFile; fileRef = F74C0434253F1CDC009762AB /* NCShares.swift */; }; F74C0437253F1CDC009762AB /* NCShares.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F74C0435253F1CDC009762AB /* NCShares.storyboard */; }; @@ -1396,6 +1400,8 @@ F749B650297B0F2400087535 /* NCManageDatabase+Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Avatar.swift"; sourceTree = ""; }; F74AF3A3247FB6AE00AC767B /* NCUtilityFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCUtilityFileSystem.swift; sourceTree = ""; }; F74B6D942A7E239A00F03C5F /* NCManageDatabase+Chunk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Chunk.swift"; sourceTree = ""; }; + F74B91E42F51D4100050813D /* InfoBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoBannerView.swift; sourceTree = ""; }; + F74B91E72F51D4510050813D /* ErrorBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorBannerView.swift; sourceTree = ""; }; F74C0434253F1CDC009762AB /* NCShares.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCShares.swift; sourceTree = ""; }; F74C0435253F1CDC009762AB /* NCShares.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCShares.storyboard; sourceTree = ""; }; F74D50342C9855A000BBBF4C /* NCCollectionViewCommon+CollectionViewDataSourcePrefetching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+CollectionViewDataSourcePrefetching.swift"; sourceTree = ""; }; @@ -2242,6 +2248,8 @@ children = ( F72CA05B2F5051DB002E2F06 /* AlertActionBannerView.swift */, F7DF7B3E2F1A2EE400514020 /* BannerView.swift */, + F74B91E42F51D4100050813D /* InfoBannerView.swift */, + F74B91E72F51D4510050813D /* ErrorBannerView.swift */, F714A1462ED84AF00050A43B /* HudBannerView.swift */, F70557BB2ED44F1800135623 /* UploadBannerView.swift */, F7DF7B412F1A36B600514020 /* HelperBanner.swift */, @@ -4180,6 +4188,7 @@ F70557BF2ED44F1800135623 /* UploadBannerView.swift in Sources */, F7817CFB29801A3500FFBC65 /* Data+Extension.swift in Sources */, F72429362AFE39860040AEF3 /* NCLivePhoto.swift in Sources */, + F74B91E82F51D45A0050813D /* ErrorBannerView.swift in Sources */, AF4BF61F27562B3F0081CEEF /* NCManageDatabase+Activity.swift in Sources */, F7CBC1262BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.swift in Sources */, F7A0D1362591FBC5008F8A13 /* String+Extension.swift in Sources */, @@ -4278,6 +4287,7 @@ AF22B217277D196700DAB0CC /* NCShareExtension+DataSource.swift in Sources */, F73EF7E22B02266D0087E6E9 /* NCManageDatabase+Trash.swift in Sources */, F77DD6AB2C5CC093009448FB /* NCSession.swift in Sources */, + F74B91E62F51D4170050813D /* InfoBannerView.swift in Sources */, F76D364728A4F8BF00214537 /* NCActivityIndicator.swift in Sources */, F749B654297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */, AF22B208277B4E4C00DAB0CC /* NCCreateFormUploadConflictCell.swift in Sources */, @@ -4638,6 +4648,7 @@ F7EFA47825ADBA500083159A /* NCViewerProviderContextMenu.swift in Sources */, F755BD9B20594AC7008C5FBB /* NCService.swift in Sources */, F376A3742E5CC6030067EE25 /* ContextMenuActions.swift in Sources */, + F74B91E92F51D45A0050813D /* ErrorBannerView.swift in Sources */, F7E8A391295DC5E0006CB2D0 /* View+Extension.swift in Sources */, F79B869B265E19D40085C0E0 /* NSMutableAttributedString+Extension.swift in Sources */, F7B7504B2397D38F004E13EC /* UIImage+Extension.swift in Sources */, @@ -4734,6 +4745,7 @@ F7864ACC2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */, F7327E302B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */, F3DDFE1E2F1F8EC600A784C8 /* ChatInputField.swift in Sources */, + F74B91E52F51D4170050813D /* InfoBannerView.swift in Sources */, F7D61EA82EBF1694007F865B /* NCManageDatabase+TableCapabilities.swift in Sources */, F79FFB262A97C24A0055EEA4 /* NCNetworkingE2EEMarkFolder.swift in Sources */, F70D8D8124A4A9BF000A5756 /* NCNetworkingProcess.swift in Sources */, diff --git a/iOSClient/GUI/Lucid Banner/BannerView.swift b/iOSClient/GUI/Lucid Banner/BannerView.swift index f6c363f25a..56c72fdae2 100644 --- a/iOSClient/GUI/Lucid Banner/BannerView.swift +++ b/iOSClient/GUI/Lucid Banner/BannerView.swift @@ -43,174 +43,16 @@ func showBanner(windowScene: UIWindowScene?, let banner = LucidBannerRegistry.shared.banner(for: windowScene) - banner.show( - payload: payload, - policy: policy) { state in - MessageBannerView(state: state) + banner.show(payload: payload, policy: policy) { state in + BannerView(state: state) } return banner } -// MARK: - Show Info - -@MainActor -func showInfoBanner(windowScene: UIWindowScene?, - title: String = "_info_", - text: String, - footnote: String? = nil, - foregroundColor: UIColor = .label, - backgroundColor: UIColor = .systemBackground, - errorCode: Int? = nil) async { - guard let windowScene else { - return - } - -#if !EXTENSION - guard !bannerContainsError(errorCode: errorCode) else { - return - } -#endif - - let banner = LucidBannerRegistry.shared.banner(for: windowScene) - - guard let window = banner.windowScene.windows.first else { - return - } - - let horizontalLayout = horizontalLayoutBanner(bounds: window.bounds, - safeAreaInsets: window.safeAreaInsets, - idiom: window.traitCollection.userInterfaceIdiom) - - let payload = LucidBannerPayload( - title: NSLocalizedString(title, comment: ""), - subtitle: NSLocalizedString(text, comment: ""), - footnote: NSLocalizedString(footnote ?? "", comment: ""), - systemImage: "checkmark.circle", - backgroundColor: Color(uiColor: backgroundColor), - textColor: Color(uiColor: foregroundColor), - imageColor: Color(uiColor: NCBrandColor.shared.customer), - vPosition: .top, - verticalMargin: 10, - horizontalLayout: horizontalLayout, - autoDismissAfter: NCGlobal.shared.dismissAfterSecond, - swipeToDismiss: true, - ) - banner.show(payload: payload) { state in - MessageBannerView(state: state) - } -} - -// MARK: - Show Error - -@MainActor -func showErrorBanner(windowScene: UIWindowScene?, - error: NKError) async { - await showErrorBanner(windowScene: windowScene, - title: "_error_", - text: error.errorDescription, - errorCode: error.errorCode) -} - -@MainActor -func showErrorBanner(windowScene: UIWindowScene?, - title: String = "_error_", - text: String, - footnote: String? = nil, - foregroundColor: UIColor = .white, - backgroundColor: UIColor = .red, - sleepBefore: Double = 1, - errorCode: Int, - afError: AFError? = nil) async { - guard let windowScene else { - return - } - -#if !EXTENSION - guard !bannerContainsError(errorCode: errorCode, afError: afError) else { - return - } -#endif - - let banner = LucidBannerRegistry.shared.banner(for: windowScene) - - guard let window = banner.windowScene.windows.first else { - return - } - let horizontalLayout = horizontalLayoutBanner(bounds: window.bounds, - safeAreaInsets: window.safeAreaInsets, - idiom: window.traitCollection.userInterfaceIdiom) - - try? await Task.sleep(for: .seconds(sleepBefore)) - - let payload = LucidBannerPayload( - title: NSLocalizedString(title, comment: ""), - subtitle: NSLocalizedString(text, comment: ""), - footnote: NSLocalizedString(footnote ?? "", comment: ""), - systemImage: "xmark.circle.fill", - backgroundColor: Color(uiColor: backgroundColor), - textColor: Color(uiColor: foregroundColor), - imageColor: .white, - vPosition: .top, - verticalMargin: 10, - horizontalLayout: horizontalLayout, - autoDismissAfter: NCGlobal.shared.dismissAfterSecond, - swipeToDismiss: true, - ) - banner.show( - payload: payload, - onTap: { _, _ in - banner.dismiss() - } - ) { state in - MessageBannerView(state: state) - } -} - -// MARK: - Helper - -#if !EXTENSION - -// Error 401 (maintenance mode) -// Error 423 (locked) -// Error 507 (insufficient storage) -// Error -1009 (NSURLErrorNotConnectedToInternet) -// Error -1003 (NSURLError​Cannot​Find​Host) - -func bannerContainsError(errorCode: Int?, afError: AFError? = nil) -> Bool { - guard let errorCode else { - return false - } - // List of errors not to be displayed - if errorCode == -999 || errorCode == 423 { - 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. - if shownErrors.contains(errorCode) { - return true - } else { - // Coalesce user-facing errors across the current foreground session. - // The same error code is shown to the user only once. - if errorCode == 401 || - errorCode == 423 || - errorCode == 507 || - errorCode == NSURLErrorNotConnectedToInternet || - errorCode == NSURLErrorCannotFindHost { - shownErrors.insert(errorCode) - } - return false - } -} -#endif - // MARK: - SwiftUI -struct MessageBannerView: View { +struct BannerView: View { @ObservedObject var state: LucidBannerState var body: some View { @@ -272,15 +114,16 @@ struct MessageBannerView: View { .foregroundStyle(.primary) .padding() - let state = LucidBannerState(payload: LucidBannerPayload( - title: "Title", - subtitle: "Subtitle", - footnote: "footnote", - systemImage: "wifi.circle", - imageAnimation: .drawOn - )) + let state = LucidBannerState( + payload: LucidBannerPayload( + title: "Title", + subtitle: "Subtitle", + footnote: "footnote", + systemImage: "wifi.circle" + ) + ) - MessageBannerView(state: state) + BannerView(state: state) .padding() } } diff --git a/iOSClient/GUI/Lucid Banner/ErrorBannerView.swift b/iOSClient/GUI/Lucid Banner/ErrorBannerView.swift new file mode 100644 index 0000000000..93ebe37f87 --- /dev/null +++ b/iOSClient/GUI/Lucid Banner/ErrorBannerView.swift @@ -0,0 +1,154 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2026 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import SwiftUI +import LucidBanner +import NextcloudKit +import Alamofire + +@MainActor +func showErrorBanner(windowScene: UIWindowScene?, + error: NKError) async { + await showErrorBanner(windowScene: windowScene, + title: "_error_", + text: error.errorDescription, + errorCode: error.errorCode) +} + +@MainActor +func showErrorBanner(windowScene: UIWindowScene?, + title: String = "_error_", + text: String, + footnote: String? = nil, + foregroundColor: UIColor = .white, + backgroundColor: UIColor = .red, + sleepBefore: Double = 1, + errorCode: Int, + afError: AFError? = nil) async { + guard let windowScene else { + return + } + +#if !EXTENSION + guard !bannerContainsError(errorCode: errorCode, afError: afError) else { + return + } +#endif + + let banner = LucidBannerRegistry.shared.banner(for: windowScene) + + guard let window = banner.windowScene.windows.first else { + return + } + + let horizontalLayout = horizontalLayoutBanner(bounds: window.bounds, + safeAreaInsets: window.safeAreaInsets, + idiom: window.traitCollection.userInterfaceIdiom) + + try? await Task.sleep(for: .seconds(sleepBefore)) + + let payload = LucidBannerPayload( + title: NSLocalizedString(title, comment: ""), + subtitle: NSLocalizedString(text, comment: ""), + footnote: NSLocalizedString(footnote ?? "", comment: ""), + systemImage: "xmark.circle.fill", + backgroundColor: Color(uiColor: backgroundColor), + textColor: Color(uiColor: foregroundColor), + imageColor: .white, + vPosition: .top, + verticalMargin: 10, + horizontalLayout: horizontalLayout, + autoDismissAfter: NCGlobal.shared.dismissAfterSecond, + swipeToDismiss: true, + ) + banner.show( + payload: payload, + onTap: { _, _ in + banner.dismiss() + } + ) { state in + ErrorBannerView(state: state) + } +} + +// MARK: - SwiftUI + +struct ErrorBannerView: View { + @ObservedObject var state: LucidBannerState + + var body: some View { + let showTitle = !(state.payload.title?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) + let showSubtitle = !(state.payload.subtitle?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) + let showFootnote = !(state.payload.footnote?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) + + containerView(state: state, coordinator: nil, allowMinimizeOnTap: false) { + VStack(spacing: 15) { + HStack(alignment: .top, spacing: 10) { + Image(systemName: state.payload.systemImage ?? "info.circle") + .applyBannerAnimation(state.payload.imageAnimation) + .font(.system(size: 30, weight: .regular)) + .foregroundStyle(state.payload.imageColor) + + VStack(alignment: .leading, spacing: 7) { + if showTitle, let title = state.payload.title { + Text(title) + .font(.subheadline.weight(.bold)) + .multilineTextAlignment(.leading) + .truncationMode(.tail) + .foregroundStyle(state.payload.textColor) + } + + if showSubtitle, let subtitle = state.payload.subtitle { + Text(subtitle) + .font(.subheadline) + .multilineTextAlignment(.leading) + .truncationMode(.tail) + .foregroundStyle(state.payload.textColor) + } + if showFootnote, let footnote = state.payload.footnote { + Text(footnote) + .font(.caption) + .multilineTextAlignment(.leading) + .truncationMode(.tail) + .foregroundStyle(state.payload.textColor) + } + } + } + } + .padding(.horizontal, 12) + .padding(.vertical, 12) + .frame(maxWidth: .infinity, alignment: .leading) + } + } +} + +// MARK: - Preview + +#Preview { + ZStack { + Text( + Array(0...500) + .map(String.init) + .joined(separator: " ") + ) + .font(.system(size: 16, design: .monospaced)) + .foregroundStyle(.primary) + .padding() + + let state = LucidBannerState( + payload: LucidBannerPayload( + title: "Error", + subtitle: "Subtitle", + footnote: "footnote", + systemImage: "xmark.circle.fill", + backgroundColor: .red, + textColor: .white, + imageColor: .white + ) + ) + + ErrorBannerView(state: state) + .padding() + } +} diff --git a/iOSClient/GUI/Lucid Banner/HelperBanner.swift b/iOSClient/GUI/Lucid Banner/HelperBanner.swift index 469fa148a8..25c7ab24d3 100644 --- a/iOSClient/GUI/Lucid Banner/HelperBanner.swift +++ b/iOSClient/GUI/Lucid Banner/HelperBanner.swift @@ -122,3 +122,42 @@ func horizontalLayoutBanner(bounds: CGRect, return .stretch(margins: phoneSideMargin) } } + +#if !EXTENSION + +// Error 401 (maintenance mode) +// Error 423 (locked) +// Error 507 (insufficient storage) +// Error -1009 (NSURLErrorNotConnectedToInternet) +// Error -1003 (NSURLError​Cannot​Find​Host) + +func bannerContainsError(errorCode: Int?, afError: AFError? = nil) -> Bool { + guard let errorCode else { + return false + } + // List of errors not to be displayed + if errorCode == -999 || errorCode == 423 { + 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. + if shownErrors.contains(errorCode) { + return true + } else { + // Coalesce user-facing errors across the current foreground session. + // The same error code is shown to the user only once. + if errorCode == 401 || + errorCode == 423 || + errorCode == 507 || + errorCode == NSURLErrorNotConnectedToInternet || + errorCode == NSURLErrorCannotFindHost { + shownErrors.insert(errorCode) + } + return false + } +} +#endif diff --git a/iOSClient/GUI/Lucid Banner/InfoBannerView.swift b/iOSClient/GUI/Lucid Banner/InfoBannerView.swift new file mode 100644 index 0000000000..e9e09023ba --- /dev/null +++ b/iOSClient/GUI/Lucid Banner/InfoBannerView.swift @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2026 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import SwiftUI +import LucidBanner +import NextcloudKit +import Alamofire + +@MainActor +func showInfoBanner(windowScene: UIWindowScene?, + title: String = "_info_", + text: String, + footnote: String? = nil, + foregroundColor: UIColor = .label, + backgroundColor: UIColor = .systemBackground, + errorCode: Int? = nil) async { + guard let windowScene else { + return + } + +#if !EXTENSION + guard !bannerContainsError(errorCode: errorCode) else { + return + } +#endif + + let banner = LucidBannerRegistry.shared.banner(for: windowScene) + + guard let window = banner.windowScene.windows.first else { + return + } + + let horizontalLayout = horizontalLayoutBanner(bounds: window.bounds, + safeAreaInsets: window.safeAreaInsets, + idiom: window.traitCollection.userInterfaceIdiom) + + let payload = LucidBannerPayload( + title: NSLocalizedString(title, comment: ""), + subtitle: NSLocalizedString(text, comment: ""), + footnote: NSLocalizedString(footnote ?? "", comment: ""), + systemImage: "checkmark.circle", + backgroundColor: Color(uiColor: backgroundColor), + textColor: Color(uiColor: foregroundColor), + imageColor: Color(uiColor: NCBrandColor.shared.customer), + vPosition: .top, + verticalMargin: 10, + horizontalLayout: horizontalLayout, + autoDismissAfter: NCGlobal.shared.dismissAfterSecond, + swipeToDismiss: true, + ) + + banner.show( + payload: payload, + onTap: { _, _ in + banner.dismiss() + } + ) { state in + InfoBannerView(state: state) + } +} + +// MARK: - SwiftUI + +struct InfoBannerView: View { + @ObservedObject var state: LucidBannerState + + var body: some View { + let showTitle = !(state.payload.title?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) + let showSubtitle = !(state.payload.subtitle?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) + let showFootnote = !(state.payload.footnote?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) + + containerView(state: state, coordinator: nil, allowMinimizeOnTap: false) { + VStack(spacing: 15) { + HStack(alignment: .top, spacing: 10) { + Image(systemName: state.payload.systemImage ?? "info.circle") + .applyBannerAnimation(state.payload.imageAnimation) + .font(.system(size: 30, weight: .regular)) + .foregroundStyle(state.payload.imageColor) + + VStack(alignment: .leading, spacing: 7) { + if showTitle, let title = state.payload.title { + Text(title) + .font(.subheadline.weight(.bold)) + .multilineTextAlignment(.leading) + .truncationMode(.tail) + .foregroundStyle(state.payload.textColor) + } + + if showSubtitle, let subtitle = state.payload.subtitle { + Text(subtitle) + .font(.subheadline) + .multilineTextAlignment(.leading) + .truncationMode(.tail) + .foregroundStyle(state.payload.textColor) + } + if showFootnote, let footnote = state.payload.footnote { + Text(footnote) + .font(.caption) + .multilineTextAlignment(.leading) + .truncationMode(.tail) + .foregroundStyle(state.payload.textColor) + } + } + } + } + .padding(.horizontal, 12) + .padding(.vertical, 12) + .frame(maxWidth: .infinity, alignment: .leading) + } + } +} + +// MARK: - Preview + +#Preview { + ZStack { + Text( + Array(0...500) + .map(String.init) + .joined(separator: " ") + ) + .font(.system(size: 16, design: .monospaced)) + .foregroundStyle(.primary) + .padding() + + let state = LucidBannerState( + payload: LucidBannerPayload( + title: "Info", + subtitle: "Subtitle", + footnote: "footnote", + systemImage: "checkmark.circle", + backgroundColor: Color(uiColor: .systemBackground), + textColor: Color(uiColor: .label), + imageColor: Color(uiColor: NCBrandColor.shared.customer) + ) + ) + + InfoBannerView(state: state) + .padding() + } +} From 4875f043b2edf2edab631410916779be35c87968 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 14:57:22 +0100 Subject: [PATCH 18/20] clean Signed-off-by: Marino Faggiana --- iOSClient/GUI/Lucid Banner/HelperBanner.swift | 1 + iOSClient/GUI/Lucid Banner/HudBannerView.swift | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/iOSClient/GUI/Lucid Banner/HelperBanner.swift b/iOSClient/GUI/Lucid Banner/HelperBanner.swift index 25c7ab24d3..df9f135f8c 100644 --- a/iOSClient/GUI/Lucid Banner/HelperBanner.swift +++ b/iOSClient/GUI/Lucid Banner/HelperBanner.swift @@ -4,6 +4,7 @@ import SwiftUI import LucidBanner +import Alamofire public extension View { @ViewBuilder diff --git a/iOSClient/GUI/Lucid Banner/HudBannerView.swift b/iOSClient/GUI/Lucid Banner/HudBannerView.swift index d44fa74895..1b7b4cdba5 100644 --- a/iOSClient/GUI/Lucid Banner/HudBannerView.swift +++ b/iOSClient/GUI/Lucid Banner/HudBannerView.swift @@ -40,10 +40,12 @@ func completeHudBannerSuccess(token: Int?, banner: LucidBanner?) { guard let banner else { return } + let payload = LucidBannerPayload.Update( stage: .success, autoDismissAfter: 2 ) + banner.update(payload: payload, for: token) } @@ -52,11 +54,13 @@ func completeHudBannerError(description: String, token: Int?, banner: LucidBanne guard let banner else { return } + let payload = LucidBannerPayload.Update( subtitle: NSLocalizedString(description, comment: ""), stage: .error, autoDismissAfter: NCGlobal.shared.dismissAfterSecond ) + banner.update(payload: payload, for: token) } From 7f46711b283a5228115736476fbbd7440966c91a Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 15:03:19 +0100 Subject: [PATCH 19/20] cleaning Signed-off-by: Marino Faggiana --- iOSClient/Extensions/UIViewController+Extension.swift | 9 --------- iOSClient/Login/NCLogin.swift | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/iOSClient/Extensions/UIViewController+Extension.swift b/iOSClient/Extensions/UIViewController+Extension.swift index 4e27917d5c..6c417b9f43 100644 --- a/iOSClient/Extensions/UIViewController+Extension.swift +++ b/iOSClient/Extensions/UIViewController+Extension.swift @@ -8,15 +8,6 @@ import MessageUI import LucidBanner extension UIViewController { - var lucidBanner: LucidBanner? { - guard let scene = view.window?.windowScene, - let delegate = scene.delegate as? SceneDelegate else { - return nil - } - - return delegate.lucidBanner - } - // https://stackoverflow.com/questions/6131205/how-to-find-topmost-view-controller-on-ios @objc func topMostViewController() -> UIViewController { // Handling Modal views diff --git a/iOSClient/Login/NCLogin.swift b/iOSClient/Login/NCLogin.swift index 94d6dba521..d8b06f15eb 100644 --- a/iOSClient/Login/NCLogin.swift +++ b/iOSClient/Login/NCLogin.swift @@ -197,7 +197,7 @@ class NCLogin: UIViewController, UITextFieldDelegate, NCLoginQRCodeDelegate { let subtitle = String(format: NSLocalizedString("_add_existing_account_", comment: ""), NCBrandOptions.shared.brand) self.banner = LucidBannerRegistry.shared.banner(for: windowScene) - showAlertActionBannerView(lucidBanner: lucidBanner, + showAlertActionBannerView(lucidBanner: banner, title: title, subtitle: subtitle) { self.openShareAccountsViewController(nil) From 09d626f96862a43cb4fb849c257591deeb3e41d3 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 27 Feb 2026 15:08:15 +0100 Subject: [PATCH 20/20] clean Signed-off-by: Marino Faggiana --- iOSClient/Assistant/Chat/NCAssistantChatModel.swift | 2 +- iOSClient/Main/NCPickerViewController.swift | 6 +++--- iOSClient/Viewer/NCViewerProviderContextMenu.swift | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/iOSClient/Assistant/Chat/NCAssistantChatModel.swift b/iOSClient/Assistant/Chat/NCAssistantChatModel.swift index 4722af24da..06bf1c6330 100644 --- a/iOSClient/Assistant/Chat/NCAssistantChatModel.swift +++ b/iOSClient/Assistant/Chat/NCAssistantChatModel.swift @@ -139,7 +139,7 @@ class NCAssistantChatModel { await generateChatSession() startPollingForResponse() } else { - await showErrorBanner(windowScene: windowScene, title: "_error_", text: "_assistant_error_send_message_", errorCode: 20) + await showErrorBanner(windowScene: windowScene, title: "_error_", text: "_assistant_error_send_message_", errorCode: NCGlobal.shared.errorInternalError) } isSending = false diff --git a/iOSClient/Main/NCPickerViewController.swift b/iOSClient/Main/NCPickerViewController.swift index d788763971..eb05a89255 100644 --- a/iOSClient/Main/NCPickerViewController.swift +++ b/iOSClient/Main/NCPickerViewController.swift @@ -65,19 +65,19 @@ class NCPhotosPickerViewController: NSObject { pickerVC?.didExceedMaximumNumberOfSelection = { _ in Task { - await showErrorBanner(windowScene: self.windowScene, text: "_limited_dimension_", errorCode: 0) + await showErrorBanner(windowScene: self.windowScene, text: "_limited_dimension_", errorCode: NCGlobal.shared.errorInternalError) } } pickerVC?.handleNoAlbumPermissions = { _ in Task { - await showErrorBanner(windowScene: self.windowScene, text: "_denied_album_", errorCode: 0) + await showErrorBanner(windowScene: self.windowScene, text: "_denied_album_", errorCode: NCGlobal.shared.errorForbidden) } } pickerVC?.handleNoCameraPermissions = { _ in Task { - await showErrorBanner(windowScene: self.windowScene, text: "_denied_camera_", errorCode: 0) + await showErrorBanner(windowScene: self.windowScene, text: "_denied_camera_", errorCode: NCGlobal.shared.errorForbidden) } } diff --git a/iOSClient/Viewer/NCViewerProviderContextMenu.swift b/iOSClient/Viewer/NCViewerProviderContextMenu.swift index 4c3866c0f7..0732551cae 100644 --- a/iOSClient/Viewer/NCViewerProviderContextMenu.swift +++ b/iOSClient/Viewer/NCViewerProviderContextMenu.swift @@ -237,7 +237,7 @@ extension NCViewerProviderContextMenu: VLCMediaPlayerDelegate { NCActivityIndicator.shared.stop() Task { let windowScene = SceneManager.shared.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene - await showErrorBanner(windowScene: windowScene, text: "_error_something_wrong_", errorCode: 0) + await showErrorBanner(windowScene: windowScene, text: "_error_something_wrong_", errorCode: NCGlobal.shared.errorInternalError) } print("Played mode: ERROR") case .playing: