diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index b24d42ac75..415b7e40a6 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -1,3 +1,4 @@ + // !$*UTF8*$! { archiveVersion = 1; @@ -90,6 +91,10 @@ AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; }; AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* Shareable.swift */; }; CB3666201AF7550816B5CD6A /* NCContextMenuComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8932E90EC4278026D86CCCC9 /* NCContextMenuComment.swift */; }; + AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */; }; + B5D45E732DA5172900773929 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D45E712DA5172900773929 /* AppUpdater.swift */; }; + C04E2F232A17BB4D001BAD85 /* FilesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */; }; + D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; F310B1EF2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F310B1EE2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift */; }; F31165022F9674A1009A1E37 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = F31165012F9674A1009A1E37 /* AppIcon.icon */; }; @@ -1280,6 +1285,8 @@ AFCE353827E5DE0400FEA6C2 /* Shareable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shareable.swift; sourceTree = ""; }; B4C7A5B36D1ED178FB6B76CB /* NCContextMenuPlayerTracks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuPlayerTracks.swift; sourceTree = ""; }; BB7697C94BA14450A0867940 /* NCContextMenuProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuProfile.swift; sourceTree = ""; }; + AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShare+Helper.swift"; sourceTree = ""; }; + B5D45E712DA5172900773929 /* AppUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdater.swift; sourceTree = ""; }; C0046CDA2A17B98400D87C9D /* NextcloudUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; @@ -2142,6 +2149,14 @@ path = Advanced; sourceTree = ""; }; + B5D45E722DA5172900773929 /* AppUpdate */ = { + isa = PBXGroup; + children = ( + B5D45E712DA5172900773929 /* AppUpdater.swift */, + ); + path = AppUpdate; + sourceTree = ""; + }; C0046CDB2A17B98400D87C9D /* NextcloudUITests */ = { isa = PBXGroup; children = ( @@ -3351,6 +3366,7 @@ isa = PBXGroup; children = ( AA517BB42D66149900F8D37C /* .tx */, + B5D45E722DA5172900773929 /* AppUpdate */, F702F2CC25EE5B4F008F8E80 /* AppDelegate.swift */, F7110ADF2F9773210095AA5C /* AppDelegate+AppRefresh.swift */, F7110AE32F9774130095AA5C /* AppDelegate+AppProcessing.swift */, @@ -4695,6 +4711,7 @@ F70898692EDDB51700EF85BD /* NCSelectOpen+SelectDelegate.swift in Sources */, F7D4BF412CA2E8D800A5E746 /* TOPasscodeViewControllerAnimatedTransitioning.m in Sources */, F7D4BF422CA2E8D800A5E746 /* TOPasscodeSettingsViewController.m in Sources */, + B5D45E732DA5172900773929 /* AppUpdater.swift in Sources */, F7D4BF432CA2E8D800A5E746 /* TOPasscodeCircleImage.m in Sources */, F78026102E9CFA3700B63436 /* NCTransfersView.swift in Sources */, F7D4BF442CA2E8D800A5E746 /* TOPasscodeView.m in Sources */, diff --git a/iOSClient/AppUpdate/AppUpdater.swift b/iOSClient/AppUpdate/AppUpdater.swift new file mode 100644 index 0000000000..5f1b0e54eb --- /dev/null +++ b/iOSClient/AppUpdate/AppUpdater.swift @@ -0,0 +1,95 @@ +// +// AppUpdater.swift +// Nextcloud +// +// Created by Amrut Waghmare on 09/10/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import Foundation +import FirebaseRemoteConfig + +struct AppUpdaterKey { + static let lastUpdateCheckDate : String = "lastUpdateCheckDate" +} + +class AppUpdater { + func checkForUpdate() { + checkUpdate{ (version, isForceupdate) in + DispatchQueue.main.async { + if let version = version, let isForceupdate = isForceupdate { + if (isForceupdate) { + self.showUpdateAlert(version: version, isForceUpdate: isForceupdate) + } else { + if self.checkLastUpdate() { + self.showUpdateAlert(version: version, isForceUpdate: isForceupdate) + } + } + } + } + } + } + + func showUpdateAlert(version: String, isForceUpdate: Bool) { + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let viewControlller = appDelegate.window?.rootViewController else { return } + let descriptionMsg = String(format: NSLocalizedString("update_description", comment: ""), version) + let alert = UIAlertController(title: NSLocalizedString("update_available", comment: ""), message: descriptionMsg, preferredStyle: .alert) + let updateAction = UIAlertAction(title: NSLocalizedString("update", comment: ""), style: .default, handler: { action in + guard let url = URL(string: NCBrandOptions.shared.appStoreUrl) else { return } + UIApplication.shared.open(url) + }) + alert.addAction(updateAction) + if !isForceUpdate { + alert.addAction(UIAlertAction(title: NSLocalizedString("not_now", comment: ""), style: .default, handler: { action in + self.saveAppUpdateCheckDate() + })) + } + alert.preferredAction = updateAction + viewControlller.present(alert, animated: true, completion: {}) + } + + func checkLastUpdate() -> Bool { + if let lastUpdateCheckDate = UserDefaults.standard.object(forKey: AppUpdaterKey.lastUpdateCheckDate) as? Date { + return daysBetweenDate(from: lastUpdateCheckDate) > 7 + } else { + return true + } + } + + func checkUpdate(completion: @escaping (String?, Bool?) -> Void) { + let remoteConfig = RemoteConfig.remoteConfig() + remoteConfig.fetch(withExpirationDuration: 1) { (status, error) in + if status == .success { + remoteConfig.activate { value, error in + // Remote config values fetched successfully + let iOSVersionString = remoteConfig["ios_app_version"].stringValue ?? "default_value" + let isForcheUpdate = remoteConfig["ios_force_update"].boolValue + if let currentVersionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String, + let currentVersion = Int(currentVersionString.replacingOccurrences(of: ".", with: "")), + let iOSVersion = Int(iOSVersionString.replacingOccurrences(of: ".", with: "")) { +// if let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { + if iOSVersion != currentVersion { + // There is an update available + completion(iOSVersionString, isForcheUpdate) + } else { + completion(nil, nil) + } + } + + } + } else { + // Handle error + print("Error fetching remote config: \(error?.localizedDescription ?? "Unknown error")") + completion(nil, nil) + } + } + } + + func saveAppUpdateCheckDate() { + UserDefaults.standard.setValue(Date(), forKey: AppUpdaterKey.lastUpdateCheckDate) + } + + func daysBetweenDate(from date: Date) -> Int { + return Calendar.current.dateComponents([.day], from: date, to: Date()).day ?? 0 + } +} diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index 9687113c67..1fb61a9ab6 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -6,14 +6,12 @@ import Foundation import UIKit import NextcloudKit import WidgetKit +import SwiftEntryKit 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,11 +21,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let windowScene = (scene as? UIWindowScene) else { return } + // Ensure MoEngage is initialized for multi-scene setups +// MoEngageAnalytics.setupIfNeeded() + 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) @@ -68,6 +67,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } else { self.startNextcloud(scene: scene, withActivateSceneForAccount: false) } + +// if let tabBarController = window?.rootViewController as? UITabBarController { +// if #available(iOS 18.0, *) { +// // Forces the "Compact" (iPhone) size class so the tab bar stays at the bottom +// tabBarController.traitOverrides.horizontalSizeClass = .compact +// } +// } } private func startNextcloud(scene: UIScene, withActivateSceneForAccount activateSceneForAccount: Bool) { @@ -114,7 +120,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { NCPreferences().removeAll() if let bundleID = Bundle.main.bundleIdentifier { + let lastUpdateCheckDate = UserDefaults.standard.object(forKey: AppUpdaterKey.lastUpdateCheckDate) UserDefaults.standard.removePersistentDomain(forName: bundleID) + if lastUpdateCheckDate != nil { + UserDefaults.standard.setValue(lastUpdateCheckDate, forKey: AppUpdaterKey.lastUpdateCheckDate) + } } if NCBrandOptions.shared.disable_intro { @@ -124,7 +134,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window?.makeKeyAndVisible() } } else { - if let navigationController = UIStoryboard(name: "NCIntro", bundle: nil).instantiateInitialViewController() as? UINavigationController { + if let viewController = UIStoryboard(name: "NCIntro", bundle: nil).instantiateInitialViewController() as? NCIntroViewController { + let navigationController = UINavigationController(rootViewController: viewController) window?.rootViewController = navigationController window?.makeKeyAndVisible() } @@ -147,9 +158,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterChangeTheming, userInfo: ["account": activeTblAccount.account]) } - // Start Networking Process + // Set up networking session await NCNetworkingProcess.shared.setCurrentAccount(activeTblAccount.account) - await NCNetworkingProcess.shared.startTimer(interval: NCNetworkingProcess.shared.maxInterval) } // Set up networking session for all configured accounts @@ -184,6 +194,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // window?.rootViewController = controller window?.makeKeyAndVisible() + // Re-evaluate in-app messages after main interface is visible +// Task { @MainActor in + MoEngageAnalytics.shared.displayInAppNotificationSafely(reason: "main interface launched") +// } // if activateSceneForAccount { self.activateSceneForAccount(scene, account: activeTblAccount.account, controller: controller) @@ -197,11 +211,6 @@ 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") } @@ -220,18 +229,31 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } func sceneDidBecomeActive(_ scene: UIScene) { - hidePrivacyProtectionWindow() + + let session = SceneManager.shared.getSession(scene: scene) + let controller = SceneManager.shared.getController(scene: scene) + nkLog(info: "Scene did become active") + + let oldVersion = UserDefaults.standard.value(forKey: NCSettingsBundleHelper.SettingsBundleKeys.BuildVersionKey) as? String + AppUpdater().checkForUpdate() + AnalyticsHelper.shared.trackAppVersion(oldVersion: oldVersion) + if let userAccount = NCManageDatabase.shared.getActiveTableAccount() { + AnalyticsHelper.shared.trackUsedStorageData(quotaUsed: userAccount.quotaUsed) + } + + NCSettingsBundleHelper.setVersionAndBuildNumber() + NCSettingsBundleHelper.checkAndExecuteSettings(delay: 0.5) + + // Re-evaluate in-app messages when scene becomes active +// Task { @MainActor in + MoEngageAnalytics.shared.displayInAppNotificationSafely(reason: "scene did become active") +// } + +// DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { +// MoEngageAnalytics.shared.requestAppStoreReview() +// } - if !NextcloudKit.shared.isNetworkReachable(), - let windowScenee = SceneManager.shared.getWindow(scene: scene)?.windowScene { - Task { - await showWarningBanner(windowScene: windowScenee, - subtitle: "_network_not_available_", - systemImage: "wifi.exclamationmark.circle", - imageAnimation: .bounce, - errorCode: NSURLErrorNotConnectedToInternet) - } - } + hidePrivacyProtectionWindow() } func sceneWillResignActive(_ scene: UIScene) { @@ -243,7 +265,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } if NCPreferences().privacyScreenEnabled { - showPrivacyProtectionWindow() + if SwiftEntryKit.isCurrentlyDisplaying { + SwiftEntryKit.dismiss { + self.showPrivacyProtectionWindow() + } + } else { + showPrivacyProtectionWindow() + } } } @@ -293,7 +321,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { return true } group.addTask { - try? await Task.sleep(for: .seconds(25)) + try? await Task.sleep(nanoseconds: 25 * 1_000_000_000) // ~25s return false } return await group.next() ?? false @@ -319,53 +347,21 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { return } - func getMatchedAccount(user: String, url: String, account: String? = nil) async -> tableAccount? { + func getMatchedAccount(userId: String, url: String) async -> tableAccount? { let tblAccounts = await NCManageDatabase.shared.getAllTableAccountAsync() for tblAccount in tblAccounts { - let host = URL(string: tblAccount.urlBase)?.host() ?? "" - - if (account == tblAccount.account) || (url.contains(host) && user == tblAccount.userId) { + let urlBase = URL(string: tblAccount.urlBase) + if url.contains(urlBase?.host ?? "") && userId == tblAccount.userId { await NCAccount().changeAccount(tblAccount.account, userProfile: nil, controller: controller) // wait switch account - try? await Task.sleep(for: .seconds(1)) + try? await Task.sleep(nanoseconds: 1_000_000_000) return tblAccount } } return nil } - /* - Example: nextcloud://assistant/shared-text - */ - - if scheme == global.appScheme, action == "assistant", url.path == "/shared-text" { - guard let text = NCAssistantSharedTextStore.loadAndClear() else { - return - } - - Task { @MainActor in - let capabilities = await NKCapabilities.shared.getCapabilities(for: controller.account) - if capabilities.assistantEnabled { - let inputModel = NCAssistantInputModel(initialText: text) - let assistant = NCAssistant(assistantModel: NCAssistantModel(controller: controller, inputModel: inputModel), chatModel: NCAssistantChatModel(controller: controller, inputModel: inputModel), conversationsModel: NCAssistantChatConversationsModel(controller: controller)) - let hostingController = UIHostingController(rootView: assistant) - controller.present(hostingController, animated: true, completion: nil) - } else { - try? await Task.sleep(for: .seconds(1)) - await showBanner(windowScene: scene as? UIWindowScene, - title: "_info_", - subtitle: "_no_assistant_installed_", - systemImage: "sparkles", - imageAnimation: .none, - imageColor: .systemBlue - ) - } - } - - return - } - /* Example: nextcloud://open-action?action=create-voice-memo&&user=marinofaggiana&url=https://cloud.nextcloud.com */ @@ -380,12 +376,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } Task { - if await getMatchedAccount(user: userScheme, url: urlScheme) == nil { - let message = String( - format: NSLocalizedString("account_does_not_exist", comment: ""), - userScheme, - ) - + if await getMatchedAccount(userId: userScheme, url: urlScheme) == nil { + let message = NSLocalizedString("_the_account_", comment: "") + " " + userScheme + NSLocalizedString("_of_", comment: "") + " " + urlScheme + " " + NSLocalizedString("_does_not_exist_", comment: "") let alertController = UIAlertController(title: NSLocalizedString("_info_", comment: ""), message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in })) @@ -437,42 +429,36 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { else if scheme == self.global.appScheme && action == "open-file" { if let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) { + var serverUrl: String = "" + var fileName: String = "" let queryItems = urlComponents.queryItems guard let userScheme = queryItems?.filter({ $0.name == "user" }).first?.value, - // let pathScheme = queryItems?.filter({ $0.name == "path" }).first?.value, - let linkScheme = queryItems?.filter({ $0.name == "link" }).first?.value else { - return - } - let domain = URL(string: linkScheme)?.host ?? "" - let accountScheme = queryItems?.filter({ $0.name == "account" }).first?.value + let pathScheme = queryItems?.filter({ $0.name == "path" }).first?.value, + let linkScheme = queryItems?.filter({ $0.name == "link" }).first?.value else { return} Task { - guard let tblAccount = await getMatchedAccount(user: userScheme, url: linkScheme, account: accountScheme) else { + guard let tblAccount = await getMatchedAccount(userId: userScheme, url: linkScheme) else { + guard let domain = URL(string: linkScheme)?.host else { return } - let message = String(format: NSLocalizedString("_account_not_available_", comment: ""), userScheme, domain) + fileName = (pathScheme as NSString).lastPathComponent + let message = String(format: NSLocalizedString("_account_not_available_", comment: ""), userScheme, domain, fileName) let alertController = UIAlertController(title: NSLocalizedString("_info_", comment: ""), message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in })) controller.present(alertController, animated: true) return } + let davFiles = "remote.php/dav/files/" + tblAccount.userId - let results = await NextcloudKit.shared.getFileFromFileIdAsync(link: linkScheme, - account: tblAccount.account) - if results.error == .success, let file = results.file { - let metadata = await NCManageDatabaseCreateMetadata().convertFileToMetadataAsync(file) - await NCManageDatabase.shared.addMetadataAsync(metadata) - if metadata.hasPreview { - let results = await NextcloudKit.shared.downloadPreviewAsync(fileId: metadata.fileId, etag: metadata.etag, account: metadata.account) - if results.error == .success, - let data = results.responseData?.data { - NCUtility().createImageFileFrom(data: data, metadata: metadata) - } - } - await NCNetworking.shared.openFileView(serverUrl: metadata.serverUrl, - metadata: metadata, - sceneIdentifier: controller.sceneIdentifier) + if pathScheme.contains("/") { + fileName = (pathScheme as NSString).lastPathComponent + serverUrl = tblAccount.urlBase + "/" + davFiles + "/" + (pathScheme as NSString).deletingLastPathComponent + } else { + fileName = pathScheme + serverUrl = tblAccount.urlBase + "/" + davFiles } + + NCNetworking.shared.openFileViewInFolder(serverUrl: serverUrl, fileNameBlink: nil, fileNameOpen: fileName, sceneIdentifier: controller.sceneIdentifier) } } @@ -491,18 +477,29 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } Task { - _ = await getMatchedAccount(user: userScheme, url: urlScheme) + _ = await getMatchedAccount(userId: userScheme, url: urlScheme) } } else if let action { if DeepLink(rawValue: action) != nil { NCDeepLinkHandler().parseDeepLink(url, controller: controller) } } else { - scene.open(url, options: nil) + let applicationHandle = NCApplicationHandle() + let isHandled = applicationHandle.applicationOpenURL(url) + if isHandled { + return + } else { + scene.open(url, options: nil) + } } } private func showPrivacyProtectionWindow() { + guard privacyProtectionWindow == nil else { + privacyProtectionWindow?.isHidden = false + return + } + guard let windowScene = self.window?.windowScene else { return } @@ -537,16 +534,19 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } + // Re-evaluate in-app messages after activating scene for account + MoEngageAnalytics.shared.displayInAppNotificationSafely(reason: "activated scene for account") + Task { - try? await Task.sleep(for: .seconds(1)) + try? await Task.sleep(nanoseconds: 1_000_000_000) let num = await NCAutoUpload.shared.initAutoUpload() nkLog(start: "Auto upload with \(num) photo") - try? await Task.sleep(for: .seconds(1.5)) + try? await Task.sleep(nanoseconds: 1_500_000_000) await NCService().startRequestServicesServer(account: account, controller: controller) - try? await Task.sleep(for: .seconds(2)) + try? await Task.sleep(nanoseconds: 2_000_000_000) await NCNetworking.shared.verifyZombie() } @@ -597,8 +597,7 @@ extension SceneDelegate: NCAccountRequestDelegate { // MARK: - Scene Manager -@MainActor -final class SceneManager { +final class SceneManager: @unchecked Sendable { static let shared = SceneManager() private var sceneController: [NCMainTabBarController: UIScene] = [:] @@ -631,9 +630,7 @@ final class SceneManager { } func getWindow(scene: UIScene?) -> UIWindow? { - guard let windowScene = scene as? UIWindowScene else { return nil } - - return windowScene.keyWindow + return (scene as? UIWindowScene)?.keyWindow } func getWindow(controller: UITabBarController?) -> UIWindow? { @@ -665,33 +662,20 @@ final class SceneManager { .compactMap { $0 as? UIWindowScene } .first } - + func getWindow(sceneIdentifier: String?) -> UIWindow? { - // 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) - } - - // 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 - } + var mainTabBarController: NCMainTabBarController? - // Last resort: first connected window scene - if let any = UIApplication.shared.connectedScenes - .compactMap({ $0 as? UIWindowScene }) - .first, - let w = any.keyWindow { - return w + if let sceneIdentifier { + for controller in sceneController.keys { + if sceneIdentifier == controller.sceneIdentifier { + mainTabBarController = controller + } + } } - - // Absolute last resort (if you keep it) - return UIApplication.shared.mainAppWindow + guard let mainTabBarController, + let scene = sceneController[mainTabBarController] else { return UIApplication.shared.mainAppWindow } + return getWindow(scene: scene) } func getSceneIdentifier() -> [String] { @@ -707,3 +691,4 @@ final class SceneManager { return NCSession.shared.getSession(controller: controller) } } +