From b065353e7b0782df0006ebf3010f0b69650fed6e Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Fri, 13 Oct 2023 15:15:47 +0530 Subject: [PATCH 1/5] NMC 2575 - App update using remote config --- iOSClient/AppUpdate/AppUpdater.swift | 91 ++++++++++++++++++++++++++++ iOSClient/SceneDelegate.swift | 26 +++++++- 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 iOSClient/AppUpdate/AppUpdater.swift diff --git a/iOSClient/AppUpdate/AppUpdater.swift b/iOSClient/AppUpdate/AppUpdater.swift new file mode 100644 index 0000000000..9fab18b4ff --- /dev/null +++ b/iOSClient/AppUpdate/AppUpdater.swift @@ -0,0 +1,91 @@ +// +// 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 iOSVersion = remoteConfig["ios_app_version"].stringValue ?? "default_value" + let isForcheUpdate = remoteConfig["ios_force_update"].boolValue + if let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { + if iOSVersion != currentVersion { + // There is an update available + completion(iOSVersion,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..37540cf6b1 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -114,7 +114,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 { @@ -216,10 +220,23 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let session = SceneManager.shared.getSession(scene: scene) let controller = SceneManager.shared.getController(scene: scene) - activateSceneForAccount(scene, account: session.account, controller: controller) + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + Task { + if let tableAccount = await self.database.getTableAccountAsync(account: session.account) { + let num = await NCAutoUpload.shared.initAutoUpload(tblAccount: tableAccount) + nkLog(start: "Auto upload with \(num) photo") + } + } + } + AppUpdater().checkForUpdate() + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRichdocumentGrabFocus) } func sceneDidBecomeActive(_ scene: UIScene) { + let session = SceneManager.shared.getSession(scene: scene) + let controller = SceneManager.shared.getController(scene: scene) + NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Scene did become active") + hidePrivacyProtectionWindow() if !NextcloudKit.shared.isNetworkReachable(), @@ -234,6 +251,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } +// func sceneDidBecomeActive(_ scene: UIScene) { +// let session = SceneManager.shared.getSession(scene: scene) +// guard !session.account.isEmpty else { return } +// +// hidePrivacyProtectionWindow() +// } + func sceneWillResignActive(_ scene: UIScene) { nkLog(debug: "Scene will resign active") From b6733569fa446e5695236f2006872b8843a548cd Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 8 Apr 2025 14:08:35 +0530 Subject: [PATCH 2/5] NMC 2575 - App update changes --- Nextcloud.xcodeproj/project.pbxproj | 17 +++++++++++++++++ iOSClient/SceneDelegate.swift | 15 +++++++++++++++ 2 files changed, 32 insertions(+) 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/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index 37540cf6b1..7a21ca4c12 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -229,6 +229,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } AppUpdater().checkForUpdate() + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRichdocumentGrabFocus) } @@ -237,6 +238,20 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let controller = SceneManager.shared.getController(scene: scene) NextcloudKit.shared.nkCommonInstance.writeLog("[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) + +// if !NCAskAuthorization().isRequesting { +// NCPasscode.shared.hidePrivacyProtectionWindow() +// } + hidePrivacyProtectionWindow() if !NextcloudKit.shared.isNetworkReachable(), From 861a9a719c15adbb0dda4d0547d9e43a8dc0a1f3 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 30 Sep 2025 21:09:12 +0530 Subject: [PATCH 3/5] NMC 2575 - App update changes --- iOSClient/AppUpdate/AppUpdater.swift | 19 ++++++++++++++++--- iOSClient/SceneDelegate.swift | 4 ++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/iOSClient/AppUpdate/AppUpdater.swift b/iOSClient/AppUpdate/AppUpdater.swift index 9fab18b4ff..15dd5f6991 100644 --- a/iOSClient/AppUpdate/AppUpdater.swift +++ b/iOSClient/AppUpdate/AppUpdater.swift @@ -62,16 +62,29 @@ class AppUpdater { if status == .success { remoteConfig.activate { value, error in // Remote config values fetched successfully - let iOSVersion = remoteConfig["ios_app_version"].stringValue ?? "default_value" + let iOSVersionString = remoteConfig["ios_app_version"].stringValue ?? "default_value" let isForcheUpdate = remoteConfig["ios_force_update"].boolValue - if let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { + 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(iOSVersion,isForcheUpdate) + completion(iOSVersionString,isForcheUpdate) } else { completion(nil, nil) } } +// let iOSVersion = remoteConfig["ios_app_version"].stringValue ?? "default_value" +// let isForcheUpdate = remoteConfig["ios_force_update"].boolValue +// if let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { +// if iOSVersion != currentVersion { +// // There is an update available +// completion(iOSVersion,isForcheUpdate) +// } else { +// completion(nil, nil) +// } +// } } } else { // Handle error diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index 7a21ca4c12..a066634c1c 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -542,6 +542,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } private func showPrivacyProtectionWindow() { + guard privacyProtectionWindow == nil else { + privacyProtectionWindow?.isHidden = false + return + } guard let windowScene = self.window?.windowScene else { return } From efa2ecf840c88eed23a95bf0040de83c42b20f4a Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 15 Dec 2025 13:13:45 +0530 Subject: [PATCH 4/5] NMC 2575 - App update changes --- iOSClient/AppUpdate/AppUpdater.swift | 25 +++++---------- iOSClient/SceneDelegate.swift | 46 ++++++---------------------- 2 files changed, 17 insertions(+), 54 deletions(-) diff --git a/iOSClient/AppUpdate/AppUpdater.swift b/iOSClient/AppUpdate/AppUpdater.swift index 15dd5f6991..5f1b0e54eb 100644 --- a/iOSClient/AppUpdate/AppUpdater.swift +++ b/iOSClient/AppUpdate/AppUpdater.swift @@ -29,8 +29,8 @@ class AppUpdater { } } } - - func showUpdateAlert(version: String, isForceUpdate: Bool){ + + 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) @@ -47,7 +47,7 @@ class AppUpdater { 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 @@ -56,7 +56,7 @@ class AppUpdater { } } - func checkUpdate(completion: @escaping (String?, Bool?) -> Void) { + func checkUpdate(completion: @escaping (String?, Bool?) -> Void) { let remoteConfig = RemoteConfig.remoteConfig() remoteConfig.fetch(withExpirationDuration: 1) { (status, error) in if status == .success { @@ -70,21 +70,12 @@ class AppUpdater { // if let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { if iOSVersion != currentVersion { // There is an update available - completion(iOSVersionString,isForcheUpdate) + completion(iOSVersionString, isForcheUpdate) } else { completion(nil, nil) } } -// let iOSVersion = remoteConfig["ios_app_version"].stringValue ?? "default_value" -// let isForcheUpdate = remoteConfig["ios_force_update"].boolValue -// if let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { -// if iOSVersion != currentVersion { -// // There is an update available -// completion(iOSVersion,isForcheUpdate) -// } else { -// completion(nil, nil) -// } -// } + } } else { // Handle error @@ -93,11 +84,11 @@ class AppUpdater { } } } - + 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 a066634c1c..30c06e9559 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -114,11 +114,7 @@ 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 { @@ -128,7 +124,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() } @@ -211,7 +208,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func sceneWillEnterForeground(_ scene: UIScene) { hidePrivacyProtectionWindow() - + if let rootHostingController = scene.rootHostingController() { if rootHostingController.anyRootView is Maintenance { return @@ -219,7 +216,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } let session = SceneManager.shared.getSession(scene: scene) let controller = SceneManager.shared.getController(scene: scene) - + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { Task { if let tableAccount = await self.database.getTableAccountAsync(account: session.account) { @@ -229,29 +226,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } AppUpdater().checkForUpdate() - + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRichdocumentGrabFocus) + activateSceneForAccount(scene, account: session.account, controller: controller) } func sceneDidBecomeActive(_ scene: UIScene) { - let session = SceneManager.shared.getSession(scene: scene) - let controller = SceneManager.shared.getController(scene: scene) - NextcloudKit.shared.nkCommonInstance.writeLog("[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) - -// if !NCAskAuthorization().isRequesting { -// NCPasscode.shared.hidePrivacyProtectionWindow() -// } - hidePrivacyProtectionWindow() if !NextcloudKit.shared.isNetworkReachable(), @@ -266,13 +246,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } -// func sceneDidBecomeActive(_ scene: UIScene) { -// let session = SceneManager.shared.getSession(scene: scene) -// guard !session.account.isEmpty else { return } -// -// hidePrivacyProtectionWindow() -// } - func sceneWillResignActive(_ scene: UIScene) { nkLog(debug: "Scene will resign active") @@ -281,6 +254,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { return } + WidgetCenter.shared.reloadAllTimelines() + if NCPreferences().privacyScreenEnabled { showPrivacyProtectionWindow() } @@ -290,6 +265,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let app = UIApplication.shared var bgID: UIBackgroundTaskIdentifier = .invalid let isBackgroundRefreshStatus = (UIApplication.shared.backgroundRefreshStatus == .available) + // Must be outside the Task otherwise isSuspendingDatabaseOperation suspends it let session = SceneManager.shared.getSession(scene: scene) guard let tblAccount = NCManageDatabase.shared.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) else { return @@ -542,10 +518,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } private func showPrivacyProtectionWindow() { - guard privacyProtectionWindow == nil else { - privacyProtectionWindow?.isHidden = false - return - } guard let windowScene = self.window?.windowScene else { return } From ceb97dc9f54c60b3d4ab7d0c88c3152bc24fbeb3 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 16 Jun 2026 16:13:55 +0530 Subject: [PATCH 5/5] NMC 2575 - App update changes --- iOSClient/SceneDelegate.swift | 248 +++++++++++++++------------------- 1 file changed, 109 insertions(+), 139 deletions(-) diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index 30c06e9559..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 { @@ -148,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 @@ -185,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) @@ -198,17 +211,12 @@ 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") } func sceneWillEnterForeground(_ scene: UIScene) { hidePrivacyProtectionWindow() - + if let rootHostingController = scene.rootHostingController() { if rootHostingController.anyRootView is Maintenance { return @@ -216,34 +224,36 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } let session = SceneManager.shared.getSession(scene: scene) let controller = SceneManager.shared.getController(scene: scene) - - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - Task { - if let tableAccount = await self.database.getTableAccountAsync(account: session.account) { - let num = await NCAutoUpload.shared.initAutoUpload(tblAccount: tableAccount) - nkLog(start: "Auto upload with \(num) photo") - } - } - } - AppUpdater().checkForUpdate() - - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRichdocumentGrabFocus) + activateSceneForAccount(scene, account: session.account, controller: controller) } 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") - 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) - } + 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() +// } + + hidePrivacyProtectionWindow() } func sceneWillResignActive(_ scene: UIScene) { @@ -254,10 +264,14 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { return } - WidgetCenter.shared.reloadAllTimelines() - if NCPreferences().privacyScreenEnabled { - showPrivacyProtectionWindow() + if SwiftEntryKit.isCurrentlyDisplaying { + SwiftEntryKit.dismiss { + self.showPrivacyProtectionWindow() + } + } else { + showPrivacyProtectionWindow() + } } } @@ -265,7 +279,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let app = UIApplication.shared var bgID: UIBackgroundTaskIdentifier = .invalid let isBackgroundRefreshStatus = (UIApplication.shared.backgroundRefreshStatus == .available) - // Must be outside the Task otherwise isSuspendingDatabaseOperation suspends it let session = SceneManager.shared.getSession(scene: scene) guard let tblAccount = NCManageDatabase.shared.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) else { return @@ -308,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 @@ -334,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 */ @@ -395,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 })) @@ -452,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) } } @@ -506,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 } @@ -552,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() } @@ -612,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] = [:] @@ -646,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? { @@ -680,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] { @@ -722,3 +691,4 @@ final class SceneManager { return NCSession.shared.getSession(controller: controller) } } +