diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 4b3481b244..07de58511b 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 */; }; @@ -394,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 */; }; @@ -704,9 +710,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 */; }; @@ -1397,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 = ""; }; @@ -1877,6 +1882,7 @@ files = ( F7490E8B29882CE4009DCE94 /* NextcloudKit in Frameworks */, F7490E7229882BB4009DCE94 /* RealmSwift in Frameworks */, + F71221802F51AF9F00ACEB08 /* LucidBanner in Frameworks */, F760DE0D2AE66EDF0027D78A /* KeychainAccess in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1888,7 +1894,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 +1914,6 @@ F7160A7D2BE931DE0034DCB3 /* RealmSwift in Frameworks */, F760DE052AE66EBE0027D78A /* KeychainAccess in Frameworks */, F7A560462AE15D3D00BE8FD6 /* Queuer in Frameworks */, - F7B8F6162EAB7503006A70D6 /* JDStatusBarNotification in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1919,6 +1923,7 @@ files = ( F760DE0B2AE66ED80027D78A /* KeychainAccess in Frameworks */, F710FC84277B7D3500AA9FBF /* RealmSwift in Frameworks */, + F712217E2F51AF9500ACEB08 /* LucidBanner in Frameworks */, F72AD71328C24BCC006CB92D /* NextcloudKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1933,7 +1938,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 */, @@ -2244,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 */, @@ -3499,6 +3505,7 @@ F7490E7129882BB4009DCE94 /* RealmSwift */, F7490E8A29882CE4009DCE94 /* NextcloudKit */, F760DE0C2AE66EDF0027D78A /* KeychainAccess */, + F712217F2F51AF9F00ACEB08 /* LucidBanner */, ); productName = "File Provider Extension UI"; productReference = F70716E32987F81500E72C1D /* File Provider Extension UI.appex */; @@ -3529,7 +3536,6 @@ F760DE082AE66ED00027D78A /* KeychainAccess */, F74C863C2AEFBFD9009A1D4A /* LRUCache */, F33EE6E62BF4C02600CA1A51 /* NIOSSL */, - F7B8F6172EAB7516006A70D6 /* JDStatusBarNotification */, F70557B82ED44E4700135623 /* LucidBanner */, ); productName = "Share Ext"; @@ -3556,7 +3562,6 @@ F760DE042AE66EBE0027D78A /* KeychainAccess */, F7160A7C2BE931DE0034DCB3 /* RealmSwift */, F33EE6E22BF4C00700CA1A51 /* NIOSSL */, - F7B8F6152EAB7503006A70D6 /* JDStatusBarNotification */, ); productName = DashboardWidgetExtension; productReference = F7346E1028B0EF5B006CE2D2 /* Widget.appex */; @@ -3580,6 +3585,7 @@ F710FC83277B7D3500AA9FBF /* RealmSwift */, F72AD71228C24BCC006CB92D /* NextcloudKit */, F760DE0A2AE66ED80027D78A /* KeychainAccess */, + F712217D2F51AF9500ACEB08 /* LucidBanner */, ); productName = "File Provider Extension"; productReference = F771E3D020E2392D00AFB62D /* File Provider Extension.appex */; @@ -3636,7 +3642,6 @@ F3374AF32D78B01B002A38F9 /* DequeModule */, F3374AF52D78B01B002A38F9 /* HashTreeCollections */, F3374AF72D78B01B002A38F9 /* HeapModule */, - F7B8F6132EAB64AD006A70D6 /* JDStatusBarNotification */, F70557B62ED44E2700135623 /* LucidBanner */, ); productName = "Crypto Cloud"; @@ -3816,7 +3821,6 @@ F33EE6EE2BF4C0FF00CA1A51 /* XCRemoteSwiftPackageReference "swift-nio" */, F7D4BF4E2CA2ECCB00A5E746 /* XCRemoteSwiftPackageReference "vlckit-spm" */, F3374AEE2D78B01B002A38F9 /* XCRemoteSwiftPackageReference "swift-collections" */, - F7B8F6122EAB64AD006A70D6 /* XCRemoteSwiftPackageReference "JDStatusBarNotification" */, F70557B52ED44E2700135623 /* XCRemoteSwiftPackageReference "LucidBanner" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; @@ -4184,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 */, @@ -4282,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 */, @@ -4642,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 */, @@ -4738,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 */, @@ -5729,7 +5737,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -5795,7 +5803,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -5972,7 +5980,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/marinofaggiana/LucidBanner"; requirement = { - branch = main; + branch = variant; kind = branch; }; }; @@ -6096,14 +6104,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"; @@ -6352,6 +6352,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" */; @@ -6522,21 +6532,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/Share/NCShareExtension.swift b/Share/NCShareExtension.swift index b9979fc629..02f9c35c15 100644 --- a/Share/NCShareExtension.swift +++ b/Share/NCShareExtension.swift @@ -49,6 +49,7 @@ 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 @@ -114,6 +115,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 +388,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 +402,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 +411,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 +449,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 +470,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 +482,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 +504,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..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,7 +88,9 @@ class NCActivity: UIViewController, NCSharePagingContent { self.loadComments() } else { Task { - await showErrorBanner(controller: self.tabBarController, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, + text: error.errorDescription, + errorCode: error.errorCode) } } } @@ -424,7 +431,9 @@ 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) + await showErrorBanner(windowScene: self.windowScene, + text: error.errorDescription, + errorCode: error.errorCode) } } diff --git a/iOSClient/Activity/NCActivityTableViewCell.swift b/iOSClient/Activity/NCActivityTableViewCell.swift index 35bd4932f0..3e3ec8737f 100644 --- a/iOSClient/Activity/NCActivityTableViewCell.swift +++ b/iOSClient/Activity/NCActivityTableViewCell.swift @@ -82,7 +82,9 @@ 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) + 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 140f50870d..06bf1c6330 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 @@ -96,7 +101,7 @@ 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) + await showErrorBanner(windowScene: windowScene, title: "_error_", text: "_assistant_error_load_messages_", errorCode: result.error.errorCode) } } @@ -107,7 +112,7 @@ import NextcloudKit if result.error != .success { stopPolling() - await showErrorBanner(controller: controller, title: "_error_", text: "_assistant_error_generate_response_", errorCode: result.error.errorCode) + await showErrorBanner(windowScene: windowScene, title: "_error_", text: "_assistant_error_generate_response_", errorCode: result.error.errorCode) return } @@ -134,7 +139,7 @@ import NextcloudKit await generateChatSession() startPollingForResponse() } else { - await showErrorBanner(controller: controller, 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/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/Extensions/UIViewController+Extension.swift b/iOSClient/Extensions/UIViewController+Extension.swift index 4f9cc274ba..6c417b9f43 100644 --- a/iOSClient/Extensions/UIViewController+Extension.swift +++ b/iOSClient/Extensions/UIViewController+Extension.swift @@ -1,30 +1,11 @@ -// -// 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 { // https://stackoverflow.com/questions/6131205/how-to-find-topmost-view-controller-on-ios diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index 6c44c327d3..9dab7eb759 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -314,16 +314,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 +334,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: errorDecodeMetadata.errorDescription, errorCode: errorDecodeMetadata.errorCode) } return (metadatas, error, reloadRequired) diff --git a/iOSClient/GUI/Lucid Banner/AlertActionBannerView.swift b/iOSClient/GUI/Lucid Banner/AlertActionBannerView.swift index b0a733dcf9..315753e128 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,18 @@ 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..56c72fdae2 100644 --- a/iOSClient/GUI/Lucid Banner/BannerView.swift +++ b/iOSClient/GUI/Lucid Banner/BannerView.swift @@ -8,34 +8,9 @@ import NextcloudKit import Alamofire // MARK: - Show Banner -#if !EXTENSION +@discardableResult @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, @@ -47,10 +22,11 @@ func showBanner(scene: UIWindowScene?, backgroundColor: UIColor, autoDismissAfter: TimeInterval = NCGlobal.shared.dismissAfterSecond, swipeToDismiss: Bool = true, - policy: LucidBanner.ShowPolicy = .enqueue) async { -#if !EXTENSION - let scene = scene ?? UIApplication.shared.mainAppWindow?.windowScene -#endif + policy: LucidBanner.ShowPolicy = .enqueue) async -> LucidBanner? { + guard let windowScene else { + return nil + } + let payload = LucidBannerPayload( title: NSLocalizedString(title ?? "", comment: ""), subtitle: NSLocalizedString(subtitle ?? "", comment: ""), @@ -65,286 +41,18 @@ func showBanner(scene: UIWindowScene?, swipeToDismiss: swipeToDismiss ) - LucidBanner.shared.show( - scene: scene, - payload: payload, - policy: policy) { state in - MessageBannerView(state: state) - } -} - -// 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?, - 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 - -@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 { - 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, - ) - LucidBanner.shared.show( - scene: scene, - 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?, - error: NKError) async { - let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene - await showErrorBanner(scene: scene, - 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) -} + let banner = LucidBannerRegistry.shared.banner(for: windowScene) -@MainActor -func showErrorBanner(sceneIdentifier: String?, - title: String = "_error_", - text: String, - footnote: String? = nil, - foregroundColor: UIColor = .white, - backgroundColor: UIColor = .red, - 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 - -@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 { - return + banner.show(payload: payload, policy: policy) { state in + BannerView(state: state) } - 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, - ) - LucidBanner.shared.show( - scene: scene, - payload: payload, - onTap: { _, _ in - LucidBanner.shared.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 - } + return banner } -#endif // MARK: - SwiftUI -struct MessageBannerView: View { +struct BannerView: View { @ObservedObject var state: LucidBannerState var body: some View { @@ -352,7 +60,7 @@ struct MessageBannerView: View { 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: nil, allowMinimizeOnTap: false) { VStack(spacing: 15) { HStack(alignment: .top, spacing: 10) { Image(systemName: state.payload.systemImage ?? "info.circle") @@ -406,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 38bd2dddd3..df9f135f8c 100644 --- a/iOSClient/GUI/Lucid Banner/HelperBanner.swift +++ b/iOSClient/GUI/Lucid Banner/HelperBanner.swift @@ -4,10 +4,12 @@ import SwiftUI import LucidBanner +import Alamofire 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 +22,7 @@ public extension View { .contentShape(Rectangle()) .onTapGesture { guard allowMinimizeOnTap else { return } - LucidBannerVariantCoordinator.shared.handleTap(state) + coordinator?.handleTap(state) } .frame(maxWidth: .infinity, alignment: .center) @@ -121,3 +123,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/HudBannerView.swift b/iOSClient/GUI/Lucid Banner/HudBannerView.swift index ec839a5cdf..1b7b4cdba5 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,42 @@ 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/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() + } +} diff --git a/iOSClient/GUI/Lucid Banner/UploadBannerView.swift b/iOSClient/GUI/Lucid Banner/UploadBannerView.swift index 0b6e1b4bb5..66efa78b45 100644 --- a/iOSClient/GUI/Lucid Banner/UploadBannerView.swift +++ b/iOSClient/GUI/Lucid Banner/UploadBannerView.swift @@ -6,21 +6,27 @@ 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 bannerCoordinator = LucidBannerVariantCoordinator(banner: banner) + + let token = banner.show(payload: payload, + policy: .drop) { state in UploadBannerView(state: state, + coordinator: bannerCoordinator, allowMinimizeOnTap: allowMinimizeOnTap, onButtonTap: onButtonTap) } #if !EXTENSION if allowMinimizeOnTap { - LucidBannerVariantCoordinator.shared.register(token: token) { _ in + bannerCoordinator.register(token: token) { _ in return .init( payloadUpdate: .init( horizontalLayout: .centered(width: 100), @@ -31,7 +37,7 @@ func showUploadBanner(scene: UIWindowScene?, } } #endif - return token + return (token, banner) } // MARK: - SwiftUI @@ -39,12 +45,14 @@ func showUploadBanner(scene: 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 } @@ -58,7 +66,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") @@ -214,6 +222,7 @@ struct UploadBannerView: View { UploadBannerView( state: state, + coordinator: nil, allowMinimizeOnTap: false ) .padding() 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/Login/NCLogin.swift b/iOSClient/Login/NCLogin.swift index f9adc983a6..d8b06f15eb 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,11 +191,13 @@ 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(scene: view.window?.windowScene, + showAlertActionBannerView(lucidBanner: banner, title: title, subtitle: subtitle) { self.openShareAccountsViewController(nil) @@ -203,7 +208,7 @@ class NCLogin: UIViewController, UITextFieldDelegate, NCLoginQRCodeDelegate { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - LucidBanner.shared.dismiss() + self.banner?.dismiss() } private func handleLoginWithAppConfig() { @@ -424,7 +429,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 +445,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..51dbb3c603 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift @@ -12,6 +12,7 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { @MainActor func didSelectMetadata(_ metadata: tableMetadata, withOcIds: Bool) async { let capabilities = await NKCapabilities.shared.getCapabilities(for: session.account) + if metadata.e2eEncrypted { if capabilities.e2EEEnabled { if !NCPreferences().isEndToEndEnabled(account: metadata.account) { @@ -21,20 +22,20 @@ 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 + 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 +53,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 +116,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..8f1c4316a3 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift @@ -75,7 +75,7 @@ extension NCCollectionViewCommon { account: self.session.account ) } else { - await showErrorBanner(controller: self.controller, + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) } @@ -117,7 +117,7 @@ extension NCCollectionViewCommon { } if results.error != .success { - await showErrorBanner(controller: self.controller, + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode, afError: results.error.error as? AFError) @@ -150,7 +150,7 @@ extension NCCollectionViewCommon { ) if results.error != .success { - await showErrorBanner(controller: self.controller, + await showErrorBanner(windowScene: windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode, afError: results.error.error as? AFError) @@ -209,7 +209,7 @@ extension NCCollectionViewCommon { ) if results.error != .success { - await showErrorBanner(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..1dbfd43dde 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift @@ -43,13 +43,13 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { if !metadatasPlain.isEmpty { let error = await self.networking.setStatusWaitDelete(metadatas: metadatasPlain) if error != .success { - await showErrorBanner(controller: self.controller, error: error) + await showErrorBanner(windowScene: self.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 +57,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 +66,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 +85,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 +103,7 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { } }) - LucidBanner.shared.dismiss() + banner?.dismiss() } await self.setEditMode(false) } @@ -167,7 +168,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..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,21 +119,29 @@ 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 ?? "" } - internal var scene: UIWindowScene? { - SceneManager.shared.getWindow(sceneIdentifier: self.controller?.sceneIdentifier)?.windowScene + @MainActor + internal var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: self.tabBarController as? NCMainTabBarController) } internal var isNumberOfItemsInAllSectionsNull: Bool { @@ -627,9 +630,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 +663,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 +691,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 +880,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/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/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/Main/NCDragDrop.swift b/iOSClient/Main/NCDragDrop.swift index 215ef1933e..d77f2e168d 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 = await 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 = await 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 = await 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..eb05a89255 100644 --- a/iOSClient/Main/NCPickerViewController.swift +++ b/iOSClient/Main/NCPickerViewController.swift @@ -11,12 +11,17 @@ import SwiftUI // MARK: - Photo Picker +@MainActor class NCPhotosPickerViewController: NSObject { var controller: NCMainTabBarController 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) { self.controller = controller @@ -60,19 +65,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: NCGlobal.shared.errorInternalError) } } pickerVC?.handleNoAlbumPermissions = { _ in Task { - await showErrorBanner(controller: self.controller, text: "_denied_album_", errorCode: 0) + await showErrorBanner(windowScene: self.windowScene, text: "_denied_album_", errorCode: NCGlobal.shared.errorForbidden) } } pickerVC?.handleNoCameraPermissions = { _ in Task { - await showErrorBanner(controller: self.controller, text: "_denied_camera_", errorCode: 0) + await showErrorBanner(windowScene: self.windowScene, text: "_denied_camera_", errorCode: NCGlobal.shared.errorForbidden) } } 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/ContextMenuActions.swift b/iOSClient/Menu/ContextMenuActions.swift index 46acf61553..b730f71e21 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 = await 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..41f002f692 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 @@ -17,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 @@ -62,7 +67,7 @@ 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) + await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } @@ -96,7 +101,7 @@ class NCContextMenuComment: NSObject { (self.viewController as? NCActivity)?.loadComments() } else { Task { @MainActor in - await showErrorBanner(controller: self.controller, 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 16915b78a9..b49e3f8d4c 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() @@ -25,7 +26,7 @@ class NCContextMenuMain: NSObject { controller?.sceneIdentifier ?? "" } - internal var scene: UIWindowScene? { + internal var windowScene: UIWindowScene? { SceneManager.shared.getWindow(sceneIdentifier: self.controller?.sceneIdentifier)?.windowScene } @@ -243,7 +244,7 @@ class NCContextMenuMain: NSObject { userId: metadata.userId ) if error != .success { - await showErrorBanner(controller: self.controller, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } @@ -281,7 +282,7 @@ class NCContextMenuMain: NSObject { await NCManageDatabase.shared.setMetadataEncryptedAsync(ocId: metadata.ocId, encrypted: false) await (self.viewController as? NCCollectionViewCommon)?.reloadDataSource() } else { - await showErrorBanner(controller: self.controller, + await showErrorBanner(windowScene: self.windowScene, title: "_e2e_error_", text: results.error.errorDescription, errorCode: results.error.errorCode) @@ -297,7 +298,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)) } } @@ -357,7 +358,7 @@ class NCContextMenuMain: NSObject { fileNameNew ) ) != nil { - await showErrorBanner(controller: self.controller, + await showErrorBanner(windowScene: self.windowScene, text: "_rename_already_exists_", errorCode: 0) return @@ -365,7 +366,7 @@ class NCContextMenuMain: NSObject { let error = await NCNetworking.shared.setStatusWaitRename(metadata, fileNameNew: fileNameNew) if error != .success { - await showErrorBanner(controller: self.controller, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } } @@ -464,19 +465,19 @@ class NCContextMenuMain: NSObject { Task { if metadata.isDirectoryE2EE { if NCNetworking.shared.isOffline { - await showErrorBanner(controller: self.controller, + await showErrorBanner(windowScene: self.windowScene, text: "_offline_not_allowed_", errorCode: NCGlobal.shared.errorOfflineNotAllowed) } else { - let token = await showHudBanner(scene: self.scene, - 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: token) + completeHudBannerSuccess(token: results.token, banner: results.banner) } else { - await completeHudBannerError(description: error.errorDescription, token: token) + completeHudBannerError(description: error.errorDescription, token: results.token, banner: results.banner) } await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in @@ -486,7 +487,7 @@ class NCContextMenuMain: NSObject { } else { let error = await NCNetworking.shared.setStatusWaitDelete(metadatas: [metadata]) if error != .success { - await showErrorBanner(controller: self.controller, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } } @@ -505,17 +506,15 @@ class NCContextMenuMain: NSObject { ) { _ in Task { @MainActor in var token: Int? + var banner: LucidBanner? if metadata.isDirectory { - token = showHudBanner( - scene: self.scene, - title: "_delete_in_progress_" - ) + (token, banner) = showHudBanner(windowScene: self.windowScene, title: "_delete_in_progress_") } 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 +522,7 @@ class NCContextMenuMain: NSObject { } }) - LucidBanner.shared.dismiss() + banner?.dismiss() } } } @@ -570,7 +569,7 @@ class NCContextMenuMain: NSObject { } } - let action = await UIAction( + let action = UIAction( title: item.name, image: iconImage ) { _ in @@ -584,12 +583,12 @@ class NCContextMenuMain: NSObject { params: item.params ) if results.error != .success { - await showErrorBanner(controller: self.controller, + await showErrorBanner(windowScene: self.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: self.windowScene, text: tooltip) } else { let baseURL = metadata.urlBase diff --git a/iOSClient/Menu/NCContextMenuPlus.swift b/iOSClient/Menu/NCContextMenuPlus.swift index 05ebf8d9d8..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,7 +94,7 @@ class NCContextMenuPlus: NSObject { capabilities: capabilities) { error in if error != .success { Task { - await showErrorBanner(controller: controller, + await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) } @@ -116,7 +120,7 @@ class NCContextMenuPlus: NSObject { capabilities: capabilities) { error in if error != .success { Task { - await showErrorBanner(controller: controller, + await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) } diff --git a/iOSClient/Menu/NCContextMenuProfile.swift b/iOSClient/Menu/NCContextMenuProfile.swift index 2857b1ee10..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,7 @@ class NCContextMenuProfile: NSObject { private func showError(_ errorKey: String) { Task { - await showErrorBanner(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..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,7 +160,7 @@ 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, + await showErrorBanner(windowScene: self.windowScene, text: "_e2e_in_upload_", errorCode: NCGlobal.shared.errorE2EEUploadInProgress) } @@ -164,7 +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 { - await showErrorBanner(controller: controller, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } return } 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/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/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/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+ServerError.swift b/iOSClient/Networking/NCNetworking+ServerError.swift index 43d9d0cac4..f64c126173 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 = await SceneManager.shared.getWindowScene(controller: controller) + await showInfoBanner(windowScene: windowScene, title: "_warning_", text: "_maintenance_mode_", errorCode: 401) 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/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 93929f64c1..50ba5fb80a 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 = await SceneManager.shared.getWindow(sceneIdentifier: metadata.sceneIdentifier)?.windowScene + 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..476d376aea 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 = await 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..6669eb4977 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -32,10 +32,16 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { var notifications: [NKNotifications] = [] var session: NCSession.Session! + @MainActor var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController } + @MainActor + internal var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: self.tabBarController as? NCMainTabBarController) + } + // MARK: - View Life Cycle override func viewDidLoad() { @@ -261,7 +267,7 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { self.tableView.reloadData() } else if error != .success { Task { - await showErrorBanner(controller: self.controller, 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.") @@ -308,7 +314,7 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { } } else if error != .success { Task { - await showErrorBanner(controller: self.controller, 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/RichWorkspace/NCRichWorkspaceCommon.swift b/iOSClient/RichWorkspace/NCRichWorkspaceCommon.swift index f69cf41682..557f2beb60 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 = await 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 = await 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 = await 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 = await SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } 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/Scan document/NCUploadScanDocument.swift b/iOSClient/Scan document/NCUploadScanDocument.swift index 8a8d4f7390..a4b894509e 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 = await 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..ffb9996caf 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") } @@ -546,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] = [:] @@ -579,7 +590,9 @@ 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? { @@ -588,21 +601,56 @@ final class SceneManager: @unchecked Sendable { return getWindow(scene: scene) } + 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) + + // Absolute last resort (if you keep it) + return UIApplication.shared.mainAppWindow } func getSceneIdentifier() -> [String] { diff --git a/iOSClient/Select/NCSelect.swift b/iOSClient/Select/NCSelect.swift index 51edc368f3..3e037170e0 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.getWindow(sceneIdentifier: sceneIdentifier)?.windowScene + 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/Select/NCSelectOpen+SelectDelegate.swift b/iOSClient/Select/NCSelectOpen+SelectDelegate.swift index faa3579793..045027f854 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 = await 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 = await SceneManager.shared.getWindowScene(controller: controller) + await showErrorBanner(windowScene: windowScene, error: error) } } } 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 \ \ 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/Settings/AutoUpload/NCAutoUploadModel.swift b/iOSClient/Settings/AutoUpload/NCAutoUploadModel.swift index a017f4aa35..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,7 +97,7 @@ class NCAutoUploadModel: ObservableObject, ViewOnAppearHandling { if value, UIApplication.shared.backgroundRefreshStatus != .available { Task { - await showInfoBanner(controller: controller, + 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 1eb80eea56..8ee4c7cb4f 100644 --- a/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift +++ b/iOSClient/Settings/E2EE/NCEndToEndInitialize.swift @@ -5,20 +5,27 @@ import UIKit import NextcloudKit +@MainActor @objc protocol NCEndToEndInitializeDelegate { func endToEndInitializeSuccess(metadata: tableMetadata?) } +@MainActor class NCEndToEndInitialize: NSObject { @objc weak var delegate: NCEndToEndInitializeDelegate? let utilityFileSystem = NCUtilityFileSystem() 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) + } + // -------------------------------------------------------------------------------------------- // MARK: Initialize // -------------------------------------------------------------------------------------------- @@ -61,14 +68,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 +94,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 +105,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 +166,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 +189,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 +203,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 +229,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 +249,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 +264,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 +300,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 +316,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 +331,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/E2EE/NCManageE2EEModel.swift b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift index e62079b62d..e264ad7c09 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 = "" @@ -15,14 +16,19 @@ 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) } + var capabilities: NKCapabilities.Capabilities { NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() } + var windowScene: UIWindowScene? { + SceneManager.shared.getWindowScene(controller: controller) + } + init(controller: NCMainTabBarController?) { super.init() self.controller = controller @@ -116,7 +122,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() @@ -127,7 +133,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) { @@ -138,7 +144,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) + } } } 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/Settings/Settings/SetupPasscodeView.swift b/iOSClient/Settings/Settings/SetupPasscodeView.swift index 21c10a49b1..2e64e829e5 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 = await 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/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/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/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/Share/NCShareNetworking.swift b/iOSClient/Share/NCShareNetworking.swift index 335c93df26..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,7 +120,7 @@ class NCShareNetworking: NSObject { NCActivityIndicator.shared.stop() } Task { - await showErrorBanner(controller: self.controller, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } self.delegate?.readShareCompleted() } @@ -170,7 +175,7 @@ class NCShareNetworking: NSObject { } } else { Task { - await showErrorBanner(controller: self.controller, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } @@ -201,7 +206,7 @@ class NCShareNetworking: NSObject { } } else { Task { - await showErrorBanner(controller: self.controller, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } } @@ -244,7 +249,7 @@ class NCShareNetworking: NSObject { } } else { Task { - await showErrorBanner(controller: self.controller, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } self.delegate?.updateShareWithError(idShare: shareable.idShare) } @@ -267,7 +272,8 @@ class NCShareNetworking: NSObject { self.delegate?.getSharees(sharees: sharees) } else { Task { - await showErrorBanner(controller: self.controller, error: error) + let windowScene = await SceneManager.shared.getWindowScene(controller: self.controller) + await showErrorBanner(windowScene: windowScene, error: error) } self.delegate?.getSharees(sharees: nil) } @@ -295,7 +301,7 @@ class NCShareNetworking: NSObject { self.delegate?.downloadLimitRemoved(by: token) } else { Task { - await showErrorBanner(controller: self.controller, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } } @@ -323,7 +329,7 @@ class NCShareNetworking: NSObject { } else { self.delegate?.downloadLimitRemoved(by: token) Task { - await showErrorBanner(controller: self.controller, error: error) + await showErrorBanner(windowScene: self.windowScene, error: error) } } } diff --git a/iOSClient/StatusMessage/NCStatusMessageModel.swift b/iOSClient/StatusMessage/NCStatusMessageModel.swift index 10b08726d0..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_" @@ -24,6 +25,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 +66,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 +83,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 +98,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 +123,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/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..e8bc6f1485 100644 --- a/iOSClient/Trash/NCTrash+Networking.swift +++ b/iOSClient/Trash/NCTrash+Networking.swift @@ -86,7 +86,7 @@ extension NCTrash { } if results.error != .success { - await showErrorBanner(controller: self.controller, 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() @@ -107,7 +107,7 @@ extension NCTrash { } } if results.error != .success { - await showErrorBanner(controller: self.controller, 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 ea7bd3489d..fef2903036 100644 --- a/iOSClient/Trash/NCTrash.swift +++ b/iOSClient/Trash/NCTrash.swift @@ -52,10 +52,17 @@ class NCTrash: UIViewController, NCTrashListCellDelegate, NCTrashGridCellDelegat NCSession.shared.getSession(controller: tabBarController) } + @MainActor var controller: NCMainTabBarController? { 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 55f50059b2..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,7 @@ import NextcloudKit if result.error == .success { selectedStatus = result.status } else { - await showErrorBanner(controller: self.controller, error: result.error) + await showErrorBanner(windowScene: windowScene, error: result.error) } } } @@ -65,7 +69,7 @@ import NextcloudKit } if result.error != .success { - await showErrorBanner(controller: self.controller, error: result.error) + await showErrorBanner(windowScene: windowScene, error: result.error) } } } @@ -80,7 +84,7 @@ import NextcloudKit } if result.error != .success { - await showErrorBanner(controller: self.controller, error: result.error) + await showErrorBanner(windowScene: windowScene, error: result.error) } await NCManageDatabase.shared.setAccountUserStatusAsync(userStatusClearAt: result.clearAt, diff --git a/iOSClient/Utility/NCOperationSaveLivePhoto.swift b/iOSClient/Utility/NCOperationSaveLivePhoto.swift index c4aaa77ecf..7ddb3c7cd7 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?) { + init(metadata: tableMetadata, metadataMOV: tableMetadata, windowScene: UIWindowScene?) { self.metadata = tableMetadata.init(value: metadata) self.metadataMOV = tableMetadata.init(value: metadataMOV) - scene = SceneManager.shared.getWindow(controller: controller)?.windowScene + self.windowScene = windowScene } 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/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/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..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,11 +161,10 @@ class NCViewerMedia: UIViewController { selector: "") else { return } - let scene = SceneManager.shared.getWindow(controller: self.tabBarController)?.windowScene var downloadRequest: DownloadRequest? - let token = showHudBanner(scene: scene, - title: "_download_in_progress_", - stage: .button) { + let (token, banner) = showHudBanner(windowScene: self.windowScene, + title: "_download_in_progress_", + stage: .button) { if let request = downloadRequest { request.cancel() } @@ -171,12 +174,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/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/NCViewerProviderContextMenu.swift b/iOSClient/Viewer/NCViewerProviderContextMenu.swift index 219dd590f3..0732551cae 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.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene + await showErrorBanner(windowScene: windowScene, text: "_error_something_wrong_", errorCode: NCGlobal.shared.errorInternalError) } 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.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } 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) } } diff --git a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLookView.swift b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLookView.swift index 816b467464..e2a3669ef7 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_") } } } @@ -55,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 @@ -63,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 47ca93a11c..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 } @@ -259,7 +261,8 @@ class NCViewerRichDocument: UIViewController, WKNavigationDelegate, WKScriptMess } } else { Task { - await showErrorBanner(sceneIdentifier: self.sceneIdentifier, text: error.errorDescription, errorCode: error.errorCode) + let windowScene = SceneManager.shared.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } }) @@ -321,7 +324,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.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } } @@ -344,7 +348,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.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene + await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) } } }