From 0c186189d92a632f06291cc50c40fcb04b5cb26a Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 17 Jul 2026 13:55:11 +0200 Subject: [PATCH 1/7] WIP Signed-off-by: Milen Pivchev --- iOSClient/Files/NCFiles.swift | 9 ++-- .../Main/NCMainNavigationController.swift | 42 +++++++++++++------ iOSClient/Menu/NCContextMenuPlus.swift | 3 +- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index e1acc03cf0..b2e5205c97 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -33,8 +33,7 @@ class NCFiles: NCCollectionViewCommon { if let userInfo = notification.userInfo, let account = userInfo["account"] as? String, self.controller?.account == account { - self.mainNavigationController?.menuPlusButton.backgroundColor = NCBrandColor.shared.getElement(account: account) - self.mainNavigationController?.menuPlusButton.tintColor = .white + self.mainNavigationController?.menuPlusButton.setPlusButtonColor(NCBrandColor.shared.getElement(account: account)) } } } @@ -63,8 +62,7 @@ class NCFiles: NCCollectionViewCommon { } if let userInfo = notification.userInfo, let account = userInfo["account"] as? String { - self.mainNavigationController?.menuPlusButton.backgroundColor = NCBrandColor.shared.getElement(account: account) - self.mainNavigationController?.menuPlusButton.tintColor = .white + self.mainNavigationController?.menuPlusButton.setPlusButtonColor(NCBrandColor.shared.getElement(account: account)) } self.navigationController?.popToRootViewController(animated: false) @@ -144,8 +142,7 @@ class NCFiles: NCCollectionViewCommon { if let menuPlusButton = self.mainNavigationController?.menuPlusButton { menuPlusButton.isEnabled = metadataFolder.isCreatable - menuPlusButton.backgroundColor = metadataFolder.isCreatable ? color : .lightGray - menuPlusButton.tintColor = .white + menuPlusButton.setPlusButtonColor(metadataFolder.isCreatable ? color : .lightGray) } } diff --git a/iOSClient/Main/NCMainNavigationController.swift b/iOSClient/Main/NCMainNavigationController.swift index 3c03ed5fa9..98fca7d6a6 100644 --- a/iOSClient/Main/NCMainNavigationController.swift +++ b/iOSClient/Main/NCMainNavigationController.swift @@ -116,19 +116,26 @@ class NCMainNavigationController: UINavigationController, UINavigationController // PLUS BUTTON MENU let buttonSize: CGFloat = 44 - let plusConfiguration = UIImage.SymbolConfiguration(pointSize: 22, weight: .regular) + let plusConfiguration = UIImage.SymbolConfiguration(pointSize: 18, weight: .regular) let plusImage = UIImage(systemName: "plus", withConfiguration: plusConfiguration)?.withRenderingMode(.alwaysTemplate) - menuPlusButton.setImage(plusImage, for: .normal) - menuPlusButton.tintColor = .white - menuPlusButton.contentHorizontalAlignment = .center - menuPlusButton.contentVerticalAlignment = .center - menuPlusButton.backgroundColor = NCBrandColor.shared.getElement(account: session.account) - menuPlusButton.layer.cornerRadius = buttonSize / 2 - menuPlusButton.layer.masksToBounds = false - menuPlusButton.layer.shadowColor = UIColor.black.cgColor - menuPlusButton.layer.shadowOpacity = 0.18 - menuPlusButton.layer.shadowRadius = 8 - menuPlusButton.layer.shadowOffset = CGSize(width: 0, height: 4) + + if #available(iOS 26.0, *) { + var glassConfiguration = UIButton.Configuration.prominentGlass() + glassConfiguration.image = plusImage + menuPlusButton.configuration = glassConfiguration + } else { + menuPlusButton.setImage(plusImage, for: .normal) + menuPlusButton.contentHorizontalAlignment = .center + menuPlusButton.contentVerticalAlignment = .center + menuPlusButton.layer.cornerRadius = buttonSize / 2 + menuPlusButton.layer.masksToBounds = false + menuPlusButton.layer.shadowColor = UIColor.black.cgColor + menuPlusButton.layer.shadowOpacity = 0.18 + menuPlusButton.layer.shadowRadius = 8 + menuPlusButton.layer.shadowOffset = CGSize(width: 0, height: 4) + } + + menuPlusButton.setPlusButtonColor(NCBrandColor.shared.getElement(account: session.account)) menuPlusButton.showsMenuAsPrimaryAction = true menuPlusButton.translatesAutoresizingMaskIntoConstraints = false menuPlusButton.accessibilityLabel = NSLocalizedString("_add_", comment: "") @@ -438,3 +445,14 @@ class NCMainNavigationController: UINavigationController, UINavigationController } } } + +extension UIButton { + func setPlusButtonColor(_ color: UIColor) { + if #available(iOS 26.0, *) { + tintColor = color + } else { + backgroundColor = color + tintColor = .white + } + } +} diff --git a/iOSClient/Menu/NCContextMenuPlus.swift b/iOSClient/Menu/NCContextMenuPlus.swift index a237728882..6db7f5f0ac 100644 --- a/iOSClient/Menu/NCContextMenuPlus.swift +++ b/iOSClient/Menu/NCContextMenuPlus.swift @@ -434,8 +434,7 @@ class NCContextMenuPlus: NSObject { menuPlusButton.menu = plusMenu menuPlusButton.showsMenuAsPrimaryAction = true - menuPlusButton.backgroundColor = NCBrandColor.shared.getElement(account: session.account) - menuPlusButton.tintColor = .white + menuPlusButton.setPlusButtonColor(NCBrandColor.shared.getElement(account: session.account)) menuPlusButton.alpha = 1 // E2EE Offline disable From b11edf4454e572bb10ca5fe06eaa546b02cb6529 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Sat, 18 Jul 2026 07:55:06 +0200 Subject: [PATCH 2/7] fix: preserve plus button tint on account changes Signed-off-by: Marino Faggiana --- iOSClient/Files/NCFiles.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index 43278c91f9..2bef64018a 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -35,7 +35,6 @@ class NCFiles: NCCollectionViewCommon { self.controller?.account == account { // (+) self.mainNavigationController?.menuPlusButton.backgroundColor = NCBrandColor.shared.getElement(account: account) - self.mainNavigationController?.menuPlusButton.tintColor = .white } } } From 17823640f636fff85d263f507e454f1185706bb2 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 20 Jul 2026 11:20:41 +0200 Subject: [PATCH 3/7] WIP Signed-off-by: Milen Pivchev --- iOSClient/Files/NCFiles.swift | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index 2bef64018a..def75aab30 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -33,8 +33,8 @@ class NCFiles: NCCollectionViewCommon { if let userInfo = notification.userInfo, let account = userInfo["account"] as? String, self.controller?.account == account { - // (+) - self.mainNavigationController?.menuPlusButton.backgroundColor = NCBrandColor.shared.getElement(account: account) + // re-tint the + button + self.mainNavigationController?.menuPlusButton.setPlusButtonColor(NCBrandColor.shared.getElement(account: account)) } } } @@ -63,9 +63,8 @@ class NCFiles: NCCollectionViewCommon { } if let userInfo = notification.userInfo, let account = userInfo["account"] as? String { - // (+) - self.mainNavigationController?.menuPlusButton.backgroundColor = NCBrandColor.shared.getElement(account: account) - self.mainNavigationController?.menuPlusButton.tintColor = .white + // re-tint the + button for the new account + self.mainNavigationController?.menuPlusButton.setPlusButtonColor(NCBrandColor.shared.getElement(account: account)) } self.navigationController?.popToRootViewController(animated: false) @@ -143,11 +142,9 @@ class NCFiles: NCCollectionViewCommon { // disable + button if no create permission let color = NCBrandColor.shared.getElement(account: self.session.account) - // (+) if let menuPlusButton = self.mainNavigationController?.menuPlusButton { menuPlusButton.isEnabled = metadataFolder.isCreatable - menuPlusButton.backgroundColor = metadataFolder.isCreatable ? color : .lightGray - menuPlusButton.tintColor = .white + menuPlusButton.setPlusButtonColor(metadataFolder.isCreatable ? color : .lightGray) } } From 97935650063c53e94392424c865d6edc0ba8a7a8 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 20 Jul 2026 12:10:08 +0200 Subject: [PATCH 4/7] WIP Signed-off-by: Milen Pivchev --- iOSClient/Files/NCFiles.swift | 13 ++++------- .../Main/NCMainNavigationController.swift | 23 ++++++++++++++++++- iOSClient/Menu/NCContextMenuPlus.swift | 19 ++++++++++++--- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index def75aab30..a5d7d6a7fa 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -34,7 +34,7 @@ class NCFiles: NCCollectionViewCommon { let account = userInfo["account"] as? String, self.controller?.account == account { // re-tint the + button - self.mainNavigationController?.menuPlusButton.setPlusButtonColor(NCBrandColor.shared.getElement(account: account)) + self.mainNavigationController?.menuPlus?.updatePlusButtonEnabled(session: NCSession.shared.getSession(account: account)) } } } @@ -64,7 +64,7 @@ class NCFiles: NCCollectionViewCommon { if let userInfo = notification.userInfo, let account = userInfo["account"] as? String { // re-tint the + button for the new account - self.mainNavigationController?.menuPlusButton.setPlusButtonColor(NCBrandColor.shared.getElement(account: account)) + self.mainNavigationController?.menuPlus?.updatePlusButtonEnabled(session: NCSession.shared.getSession(account: account)) } self.navigationController?.popToRootViewController(animated: false) @@ -139,13 +139,8 @@ class NCFiles: NCCollectionViewCommon { if let metadataFolder { nkLog(info: "Inside metadata folder \(metadataFolder.fileName) with permissions: \(metadataFolder.permissions)") - // disable + button if no create permission - let color = NCBrandColor.shared.getElement(account: self.session.account) - - if let menuPlusButton = self.mainNavigationController?.menuPlusButton { - menuPlusButton.isEnabled = metadataFolder.isCreatable - menuPlusButton.setPlusButtonColor(metadataFolder.isCreatable ? color : .lightGray) - } + // disable + button if no create permission or E2EE offline + self.mainNavigationController?.menuPlus?.updatePlusButtonEnabled(session: self.session) } let metadatas = await self.database.getMetadatasAsyncDataSource(withServerUrl: self.serverUrl, diff --git a/iOSClient/Main/NCMainNavigationController.swift b/iOSClient/Main/NCMainNavigationController.swift index e4b9979b6a..d3b2cedf31 100644 --- a/iOSClient/Main/NCMainNavigationController.swift +++ b/iOSClient/Main/NCMainNavigationController.swift @@ -12,7 +12,7 @@ class NCMainNavigationController: UINavigationController, UINavigationController let utility = NCUtility() let utilityFileSystem = NCUtilityFileSystem() let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! - let menuPlusButton = UIButton(type: .system) + let menuPlusButton: UIButton = NCMenuPlusButton(type: .system) var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController @@ -451,6 +451,27 @@ class NCMainNavigationController: UINavigationController, UINavigationController } } +private final class NCMenuPlusButton: UIButton { + // Disable hit testing if the button has low alpha, is hidden, or disabled. + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let view = super.hitTest(point, with: event) { + return view + } + + guard !isEnabled, !isHidden, alpha >= 0.01, bounds.contains(point) else { + return nil + } + + return self + } + + override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { + guard isEnabled else { return nil } + + return super.contextMenuInteraction(interaction, configurationForMenuAtLocation: location) + } +} + extension UIButton { func setPlusButtonColor(_ color: UIColor) { if #available(iOS 26.0, *) { diff --git a/iOSClient/Menu/NCContextMenuPlus.swift b/iOSClient/Menu/NCContextMenuPlus.swift index 094f644a4f..637ecc4f0e 100644 --- a/iOSClient/Menu/NCContextMenuPlus.swift +++ b/iOSClient/Menu/NCContextMenuPlus.swift @@ -427,6 +427,8 @@ class NCContextMenuPlus: NSObject { let plusMenu = UIMenu(children: plusMenuElements) // PLUS BUTTON + updatePlusButtonEnabled(session: session) + if menuPlusButton.menu != nil, !capabilitiesChanged { return @@ -434,11 +436,22 @@ class NCContextMenuPlus: NSObject { menuPlusButton.menu = plusMenu menuPlusButton.showsMenuAsPrimaryAction = true - menuPlusButton.setPlusButtonColor(NCBrandColor.shared.getElement(account: session.account)) menuPlusButton.alpha = 1 + } + + func updatePlusButtonEnabled(session: NCSession.Session) { + guard let controller, let menuPlusButton else { + return + } + + let metadataFolder = (controller.currentViewController() as? NCCollectionViewCommon)?.metadataFolder + let isDirectoryE2EE = metadataFolder?.e2eEncrypted ?? false + let isNetworkReachable = NextcloudKit.shared.isNetworkReachable() + + let isEnabled = (metadataFolder?.isCreatable ?? true) && (isNetworkReachable || !isDirectoryE2EE) - // E2EE Offline disable - menuPlusButton.isEnabled = isNetworkReachable || !isDirectoryE2EE + menuPlusButton.isEnabled = isEnabled + menuPlusButton.setPlusButtonColor(isEnabled ? NCBrandColor.shared.getElement(account: session.account) : .lightGray) } @MainActor From 14a28391011eef2791a1774c4e96dde00456a072 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 20 Jul 2026 12:34:44 +0200 Subject: [PATCH 5/7] WIP Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCContextMenuPlus.swift | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/iOSClient/Menu/NCContextMenuPlus.swift b/iOSClient/Menu/NCContextMenuPlus.swift index 637ecc4f0e..e175e29dcd 100644 --- a/iOSClient/Menu/NCContextMenuPlus.swift +++ b/iOSClient/Menu/NCContextMenuPlus.swift @@ -444,16 +444,28 @@ class NCContextMenuPlus: NSObject { return } - let metadataFolder = (controller.currentViewController() as? NCCollectionViewCommon)?.metadataFolder - let isDirectoryE2EE = metadataFolder?.e2eEncrypted ?? false - let isNetworkReachable = NextcloudKit.shared.isNetworkReachable() - - let isEnabled = (metadataFolder?.isCreatable ?? true) && (isNetworkReachable || !isDirectoryE2EE) + let isEnabled = isPlusButtonEnabled(for: controller) menuPlusButton.isEnabled = isEnabled menuPlusButton.setPlusButtonColor(isEnabled ? NCBrandColor.shared.getElement(account: session.account) : .lightGray) } + private func isPlusButtonEnabled(for controller: NCMainTabBarController) -> Bool { + guard let metadataFolder = (controller.currentViewController() as? NCCollectionViewCommon)?.metadataFolder else { + return true + } + + guard metadataFolder.isCreatable else { + return false + } + + guard metadataFolder.e2eEncrypted else { + return true + } + + return NextcloudKit.shared.isNetworkReachable() + } + @MainActor func hiddenPlusButton(isEditMode: Bool, isSearchingMode: Bool, animation: Bool = true) { if isEditMode || isSearchingMode { From 3284bbf78c76297935388097d2a0650bc9dde9ab Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 20 Jul 2026 12:46:17 +0200 Subject: [PATCH 6/7] Comment Signed-off-by: Milen Pivchev --- iOSClient/Main/NCMainNavigationController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Main/NCMainNavigationController.swift b/iOSClient/Main/NCMainNavigationController.swift index d3b2cedf31..1256b5312c 100644 --- a/iOSClient/Main/NCMainNavigationController.swift +++ b/iOSClient/Main/NCMainNavigationController.swift @@ -452,7 +452,7 @@ class NCMainNavigationController: UINavigationController, UINavigationController } private final class NCMenuPlusButton: UIButton { - // Disable hit testing if the button has low alpha, is hidden, or disabled. + // Keep hit testing so taps don't fall through if the button is disabled, hidden or low alpha. override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let view = super.hitTest(point, with: event) { return view From 38cf1e2cb3474dd762bd41e7734966ad4af901ef Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 20 Jul 2026 15:18:30 +0200 Subject: [PATCH 7/7] Refactor Signed-off-by: Milen Pivchev --- .../NCCollectionViewCommon+CollectionViewDataSource.swift | 6 +++--- .../Main/Collection Common/NCCollectionViewCommon.swift | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift index 15a7c7e460..12aa777f1e 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift @@ -20,9 +20,9 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { self.layoutForView = self.database.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) // is a Directory E2EE if isSearchingMode { - self.isDirectoryE2EE = false + self.isCurrentDirectoryE2EE = false } else { - self.isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(serverUrl: serverUrl, urlBase: session.urlBase, userId: session.userId, account: session.account) + self.isCurrentDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(serverUrl: serverUrl, urlBase: session.urlBase, userId: session.userId, account: session.account) } return self.dataSource.numberOfItemsInSection(section) } @@ -119,7 +119,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { let metadata = self.dataSource.getMetadata(indexPath: indexPath) ?? tableMetadata() // E2EE create preview - if self.isDirectoryE2EE, + if self.isCurrentDirectoryE2EE, metadata.isImageOrVideo, !utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag, userId: metadata.userId, urlBase: metadata.urlBase) { utility.createImageFileFrom(metadata: metadata) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index ce7856060d..89cd190bde 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -31,7 +31,8 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, internal var backgroundImageView = UIImageView() internal var serverUrl: String = "" internal var isEditMode = false - internal var isDirectoryE2EE = false + // whether the displayed folder is E2EE; refreshed on each collection view data-source pass + internal var isCurrentDirectoryE2EE = false internal var fileSelect: [String] = [] internal var metadataFolder: tableMetadata? internal var richWorkspaceText: String?