From 0d8040d1a157094843d8f1f0d1dc52e105578c6a Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 27 Jan 2026 12:39:43 +0100 Subject: [PATCH 01/19] WIP Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 8 ++++---- iOSClient/Login/NCLoginProvider.swift | 1 - .../NCCollectionViewCommon+CellDelegate.swift | 2 +- .../NCCollectionViewCommon+CollectionViewDelegate.swift | 2 +- .../Section Header Footer/NCSectionFirstHeader.swift | 4 ++-- iOSClient/Media/NCMedia+CollectionViewDelegate.swift | 2 +- .../Menu/{NCContextMenu.swift => NCContextMenuMain.swift} | 5 ++++- 7 files changed, 13 insertions(+), 11 deletions(-) rename iOSClient/Menu/{NCContextMenu.swift => NCContextMenuMain.swift} (98%) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 0a34015889..d0a1957839 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -623,7 +623,7 @@ F78ACD54219047D40088454D /* NCSectionFooter.xib in Resources */ = {isa = PBXBuildFile; fileRef = F78ACD53219047D40088454D /* NCSectionFooter.xib */; }; F78B87E72B62527100C65ADC /* NCMediaDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78B87E62B62527100C65ADC /* NCMediaDataSource.swift */; }; F78B87E92B62550800C65ADC /* NCMediaDownloadThumbnail.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnail.swift */; }; - F78C6FDE296D677300C952C3 /* NCContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78C6FDD296D677300C952C3 /* NCContextMenu.swift */; }; + F78C6FDE296D677300C952C3 /* NCContextMenuMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78C6FDD296D677300C952C3 /* NCContextMenuMain.swift */; }; F78E2D6529AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; }; F78E2D6629AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; }; F78E2D6729AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; }; @@ -1544,7 +1544,7 @@ F78ACD53219047D40088454D /* NCSectionFooter.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCSectionFooter.xib; sourceTree = ""; }; F78B87E62B62527100C65ADC /* NCMediaDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaDataSource.swift; sourceTree = ""; }; F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaDownloadThumbnail.swift; sourceTree = ""; }; - F78C6FDD296D677300C952C3 /* NCContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenu.swift; sourceTree = ""; }; + F78C6FDD296D677300C952C3 /* NCContextMenuMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuMain.swift; sourceTree = ""; }; F78D6F461F0B7CB9002F9619 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Localizable.strings"; sourceTree = ""; }; F78D6F4D1F0B7CE4002F9619 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nb-NO"; path = "nb-NO.lproj/Localizable.strings"; sourceTree = ""; }; F78D6F541F0B7D47002F9619 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; @@ -2001,7 +2001,7 @@ isa = PBXGroup; children = ( F376A3732E5CC5FF0067EE25 /* ContextMenuActions.swift */, - F78C6FDD296D677300C952C3 /* NCContextMenu.swift */, + F78C6FDD296D677300C952C3 /* NCContextMenuMain.swift */, 3704EB2923D5A58400455C5B /* NCMenu.storyboard */, 371B5A2D23D0B04500FAFAE9 /* NCMenu.swift */, AF935066276B84E700BD078F /* NCMenu+FloatingPanel.swift */, @@ -4424,7 +4424,7 @@ buildActionMask = 2147483647; files = ( F77444F522281649000D5EB0 /* NCMediaCell.swift in Sources */, - F78C6FDE296D677300C952C3 /* NCContextMenu.swift in Sources */, + F78C6FDE296D677300C952C3 /* NCContextMenuMain.swift in Sources */, F7E402332BA89551007E5609 /* NCTrash+Networking.swift in Sources */, F7E7AEA72BA32D0000512E52 /* NCCollectionViewUnifiedSearch.swift in Sources */, F73EF7A72B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */, diff --git a/iOSClient/Login/NCLoginProvider.swift b/iOSClient/Login/NCLoginProvider.swift index 3e3442a060..bfe753fecb 100644 --- a/iOSClient/Login/NCLoginProvider.swift +++ b/iOSClient/Login/NCLoginProvider.swift @@ -6,7 +6,6 @@ import UIKit @preconcurrency import WebKit import NextcloudKit -import FloatingPanel protocol NCLoginProviderDelegate: AnyObject { /// diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift index 4aabde59f7..84c2171478 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CellDelegate.swift @@ -2,7 +2,7 @@ extension NCCollectionViewCommon: NCListCellDelegate, NCGridCellDelegate, NCPhot func contextMenu(with metadata: tableMetadata?, button: UIButton, sender: Any) { Task { guard let metadata else { return } - button.menu = NCContextMenu(metadata: metadata, viewController: self, sceneIdentifier: self.sceneIdentifier, sender: sender).viewMenu() + button.menu = NCContextMenuMain(metadata: metadata, viewController: self, sceneIdentifier: self.sceneIdentifier, sender: sender).viewMenu() } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift index 8b18f1e11e..c86b8daadf 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift @@ -171,7 +171,7 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { return UIContextMenuConfiguration(identifier: identifier, previewProvider: { return nil }, actionProvider: { _ in - let contextMenu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, sender: cell) + let contextMenu = NCContextMenuMain(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, sender: cell) return contextMenu.viewMenu() }) } diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift index bedfb4a1e1..ebb23c677a 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFirstHeader.swift @@ -249,7 +249,7 @@ extension NCSectionFirstHeader: UICollectionViewDelegate { return NCViewerProviderContextMenu(metadata: metadata, image: image, sceneIdentifier: self.sceneIdentifier) }, actionProvider: { _ in let cell = collectionView.cellForItem(at: indexPath) - let contextMenu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: viewController, sceneIdentifier: self.sceneIdentifier, sender: cell) + let contextMenu = NCContextMenuMain(metadata: metadata.detachedCopy(), viewController: viewController, sceneIdentifier: self.sceneIdentifier, sender: cell) return contextMenu.viewMenu() }) #endif @@ -271,7 +271,7 @@ extension NCSectionFirstHeader: NCRecommendationsCellDelegate { guard let viewController = self.viewController, let metadata else { return } - button.menu = NCContextMenu(metadata: metadata, viewController: viewController, sceneIdentifier: self.sceneIdentifier, sender: sender).viewMenu() + button.menu = NCContextMenuMain(metadata: metadata, viewController: viewController, sceneIdentifier: self.sceneIdentifier, sender: sender).viewMenu() } #endif } diff --git a/iOSClient/Media/NCMedia+CollectionViewDelegate.swift b/iOSClient/Media/NCMedia+CollectionViewDelegate.swift index c5113c7ab4..71a9c59400 100644 --- a/iOSClient/Media/NCMedia+CollectionViewDelegate.swift +++ b/iOSClient/Media/NCMedia+CollectionViewDelegate.swift @@ -44,7 +44,7 @@ extension NCMedia: UICollectionViewDelegate { return UIContextMenuConfiguration(identifier: identifier, previewProvider: { return NCViewerProviderContextMenu(metadata: metadata, image: image, sceneIdentifier: self.sceneIdentifier) }, actionProvider: { _ in - let contextMenu = NCContextMenu(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, sender: collectionView) + let contextMenu = NCContextMenuMain(metadata: metadata.detachedCopy(), viewController: self, sceneIdentifier: self.sceneIdentifier, sender: collectionView) return contextMenu.viewMenu() }) } diff --git a/iOSClient/Menu/NCContextMenu.swift b/iOSClient/Menu/NCContextMenuMain.swift similarity index 98% rename from iOSClient/Menu/NCContextMenu.swift rename to iOSClient/Menu/NCContextMenuMain.swift index fe2e5e303c..80747e5737 100644 --- a/iOSClient/Menu/NCContextMenu.swift +++ b/iOSClient/Menu/NCContextMenuMain.swift @@ -11,7 +11,10 @@ import NextcloudKit import SVGKit import LucidBanner -class NCContextMenu: NSObject { +/// A context menu used in ``NCCollectionViewCommon`` and ``NCMedia`` +/// See ``NCCollectionViewCommon/collectionView(_:contextMenuConfigurationForItemAt:point:)``, +/// ``NCCollectionViewCommon/contextMenu(with:button:sender:)``, ``NCMedia/collectionView(_:contextMenuConfigurationForItemAt:point:)`` for usage details. +class NCContextMenuMain: NSObject { let utilityFileSystem = NCUtilityFileSystem() let utility = NCUtility() From ab17b9a265dcd91da1b20769d90dd0ee54086c13 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 27 Jan 2026 13:12:34 +0100 Subject: [PATCH 02/19] WIP Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCNotification+Menu.swift | 34 ++++++++++++--------- iOSClient/Notification/NCNotification.swift | 9 +++++- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/iOSClient/Menu/NCNotification+Menu.swift b/iOSClient/Menu/NCNotification+Menu.swift index ea469e6c16..9437c80109 100644 --- a/iOSClient/Menu/NCNotification+Menu.swift +++ b/iOSClient/Menu/NCNotification+Menu.swift @@ -22,30 +22,34 @@ // import UIKit -import FloatingPanel import SwiftyJSON import NextcloudKit -extension NCNotification { - func toggleMenu(notification: NKNotifications, sender: Any?) { - var actions = [NCMenuAction]() +class NCContextMenuNotification: NSObject { + let notification: NKNotifications + weak var delegate: NCNotificationCellDelegate? - if let notificationActions = notification.actions, let jsonNotificationActionsActions = JSON(notificationActions).array { - for action in jsonNotificationActionsActions { + init(notification: NKNotifications, delegate: NCNotificationCellDelegate?) { + self.notification = notification + self.delegate = delegate + } + + func viewMenu() -> UIMenu { + var actions: [UIAction] = [] + + if let notificationActions = notification.actions, + let jsonActions = JSON(notificationActions).array { + for action in jsonActions { let label = action["label"].stringValue actions.append( - NCMenuAction( - title: action["label"].stringValue, - icon: UIImage(), - sender: sender, - action: { _ in - self.tapAction(with: notification, label: label, sender: sender) - } - ) + UIAction(title: label, image: nil) { [weak self] _ in + guard let self else { return } + self.delegate?.tapAction(with: self.notification, label: label, sender: nil) + } ) } } - presentMenu(with: actions, sender: sender) + return UIMenu(title: "", children: actions) } } diff --git a/iOSClient/Notification/NCNotification.swift b/iOSClient/Notification/NCNotification.swift index d1c5825db0..0fa356fd12 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -224,6 +224,13 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { cell.more.isEnabled = true cell.more.isHidden = false cell.more.setTitle("…", for: .normal) + + let contextMenu = NCContextMenuNotification( + notification: notification, + delegate: self + ) + cell.more.menu = contextMenu.viewMenu() + cell.more.showsMenuAsPrimaryAction = true } var buttonWidth = max(cell.primary.intrinsicContentSize.width, cell.secondary.intrinsicContentSize.width) @@ -310,7 +317,7 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { } func tapMore(with notification: NKNotifications, sender: Any?) { - toggleMenu(notification: notification, sender: sender) +// toggleMenu(notification: notification, sender: sender) } // MARK: - Load notification networking From 143fd57a28c317c5d7c6c61ba8df8dc58f4ffdf0 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 27 Jan 2026 13:45:52 +0100 Subject: [PATCH 03/19] WIP Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCTrash+Menu.swift | 95 +++++++------------- iOSClient/Trash/NCTrash+CollectionView.swift | 11 +++ iOSClient/Trash/NCTrash.swift | 12 ++- 3 files changed, 47 insertions(+), 71 deletions(-) diff --git a/iOSClient/Menu/NCTrash+Menu.swift b/iOSClient/Menu/NCTrash+Menu.swift index cd983d78a9..b39a432178 100644 --- a/iOSClient/Menu/NCTrash+Menu.swift +++ b/iOSClient/Menu/NCTrash+Menu.swift @@ -24,77 +24,44 @@ // import UIKit -import FloatingPanel import NextcloudKit -extension NCTrash { - func toggleMenuMore(with objectId: String, image: UIImage?, isGridCell: Bool, sender: Any?) { - guard let tblTrash = self.database.getTableTrash(fileId: objectId, account: session.account) - else { - return - } - guard isGridCell - else { - let alert = UIAlertController(title: NSLocalizedString("_want_delete_", comment: ""), message: tblTrash.trashbinFileName, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("_delete_", comment: ""), style: .destructive, handler: { _ in - Task { - await self.deleteItems(with: [objectId]) - } - })) - alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel)) - self.present(alert, animated: true, completion: nil) - return - } +class NCContextMenuTrash: NSObject { + let objectId: String + let utility = NCUtility() + weak var trashController: NCTrash? + + init(objectId: String, trashController: NCTrash?) { + self.objectId = objectId + self.trashController = trashController + } - var actions: [NCMenuAction] = [] + func viewMenu() -> UIMenu { + var actions: [UIMenuElement] = [] - var iconHeader: UIImage! - if let icon = utility.getImage(ocId: tblTrash.fileId, etag: tblTrash.fileName, ext: NCGlobal.shared.previewExt512, userId: session.userId, urlBase: session.urlBase) { - iconHeader = icon - } else { - if tblTrash.directory { - iconHeader = NCImageCache.shared.getFolder(account: tblTrash.account) - } else { - iconHeader = NCImageCache.shared.getImageFile() + let restoreAction = UIAction( + title: NSLocalizedString("_restore_", comment: ""), + image: utility.loadImage(named: "arrow.counterclockwise", colors: [NCBrandColor.shared.iconImageColor]) + ) { [weak self] _ in + guard let self, let controller = self.trashController else { return } + Task { + await controller.restoreItem(with: self.objectId) } } + actions.append(restoreAction) - actions.append( - NCMenuAction( - title: tblTrash.trashbinFileName, - icon: iconHeader, - sender: sender, - action: nil - ) - ) - - actions.append( - NCMenuAction( - title: NSLocalizedString("_restore_", comment: ""), - icon: utility.loadImage(named: "arrow.counterclockwise", colors: [NCBrandColor.shared.iconImageColor]), - sender: sender, - action: { _ in - Task { - await self.restoreItem(with: objectId) - } - } - ) - ) - - actions.append( - NCMenuAction( - title: NSLocalizedString("_delete_", comment: ""), - destructive: true, - icon: utility.loadImage(named: "trash", colors: [.red]), - sender: sender, - action: { _ in - Task { - await self.deleteItems(with: [objectId]) - } - } - ) - ) + let deleteAction = UIAction( + title: NSLocalizedString("_delete_", comment: ""), + image: utility.loadImage(named: "trash", colors: [.red]), + attributes: .destructive + ) { [weak self] _ in + guard let self, let controller = self.trashController else { return } + Task { + await controller.deleteItems(with: [self.objectId]) + } + } + actions.append(deleteAction) - presentMenu(with: actions, controller: controller, sender: sender) + return UIMenu(title: "", children: actions) } } diff --git a/iOSClient/Trash/NCTrash+CollectionView.swift b/iOSClient/Trash/NCTrash+CollectionView.swift index 8995ce177c..3a5e62546d 100644 --- a/iOSClient/Trash/NCTrash+CollectionView.swift +++ b/iOSClient/Trash/NCTrash+CollectionView.swift @@ -69,6 +69,17 @@ extension NCTrash: UICollectionViewDataSource { gridCell.delegate = self cell = gridCell } + + if let resultTableTrash = datasource?[indexPath.item] { + let contextMenu = NCContextMenuTrash(objectId: resultTableTrash.fileId, trashController: self) + if let listCell = cell as? NCTrashListCell { + listCell.buttonMore.menu = contextMenu.viewMenu() + listCell.buttonMore.showsMenuAsPrimaryAction = true + } else if let gridCell = cell as? NCTrashGridCell { + gridCell.buttonMore.menu = contextMenu.viewMenu() + gridCell.buttonMore.showsMenuAsPrimaryAction = true + } + } guard let resultTableTrash = datasource?[indexPath.item] else { return cell } cell.imageItem.contentMode = .scaleAspectFit diff --git a/iOSClient/Trash/NCTrash.swift b/iOSClient/Trash/NCTrash.swift index 4a79337bfc..45937ff2b5 100644 --- a/iOSClient/Trash/NCTrash.swift +++ b/iOSClient/Trash/NCTrash.swift @@ -139,19 +139,17 @@ class NCTrash: UIViewController, NCTrashListCellDelegate, NCTrashGridCellDelegat } func tapMoreListItem(with objectId: String, image: UIImage?, sender: Any) { - if !isEditMode { - toggleMenuMore(with: objectId, image: image, isGridCell: false, sender: sender) - } else if let button = sender as? UIView { + // Menu is now shown via native context menu on the button + if isEditMode, let button = sender as? UIView { let buttonPosition = button.convert(CGPoint.zero, to: collectionView) let indexPath = collectionView.indexPathForItem(at: buttonPosition) collectionView(self.collectionView, didSelectItemAt: indexPath!) - } // else: undefined sender + } } func tapMoreGridItem(with objectId: String, image: UIImage?, sender: Any) { - if !isEditMode { - toggleMenuMore(with: objectId, image: image, isGridCell: true, sender: sender) - } else if let button = sender as? UIView { + // Menu is now shown via native context menu on the button + if isEditMode, let button = sender as? UIView { let buttonPosition = button.convert(CGPoint.zero, to: collectionView) let indexPath = collectionView.indexPathForItem(at: buttonPosition) collectionView(self.collectionView, didSelectItemAt: indexPath!) From 9e1df7e1add43946f8b23deec7027eb4ae91928b Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 27 Jan 2026 19:14:28 +0100 Subject: [PATCH 04/19] WIP Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCShare+Menu.swift | 270 ++++++++++--------- iOSClient/Menu/NCTrash+Menu.swift | 14 +- iOSClient/Share/NCShare+NCCellDelegate.swift | 9 +- iOSClient/Share/NCShare.swift | 11 + iOSClient/Share/NCShareLinkCell.swift | 40 ++- iOSClient/Share/NCShareUserCell.swift | 21 +- iOSClient/Trash/NCTrash+CollectionView.swift | 19 +- 7 files changed, 228 insertions(+), 156 deletions(-) diff --git a/iOSClient/Menu/NCShare+Menu.swift b/iOSClient/Menu/NCShare+Menu.swift index 9ac9b5c5b9..c29f609291 100644 --- a/iOSClient/Menu/NCShare+Menu.swift +++ b/iOSClient/Menu/NCShare+Menu.swift @@ -25,149 +25,162 @@ import Foundation import UIKit import NextcloudKit -extension NCShare { - func toggleShareMenu(for share: tableShare, sender: Any?) { - let capabilities = NCNetworking.shared.capabilities[self.metadata.account] ?? NKCapabilities.Capabilities() - var actions = [NCMenuAction]() +class NCContextMenuShare: NSObject { + let share: tableShare + let isDirectory: Bool + let canReshare: Bool + let utility = NCUtility() + let database = NCManageDatabase.shared + let shareController: NCShare + + init(share: tableShare, isDirectory: Bool, canReshare: Bool, shareController: NCShare) { + self.share = share + self.isDirectory = isDirectory + self.canReshare = canReshare + self.shareController = shareController + } + func viewMenu() -> UIMenu { + var actions: [UIMenuElement] = [] + + // Add share link (only for public links with reshare permission) if share.shareType == NKShare.ShareType.publicLink.rawValue, canReshare { - actions.append( - NCMenuAction( - title: NSLocalizedString("_share_add_sharelink_", comment: ""), - icon: utility.loadImage(named: "plus", colors: [NCBrandColor.shared.iconImageColor]), - sender: sender, - action: { _ in - self.makeNewLinkShare() - } - ) - ) + let addLinkAction = UIAction( + title: NSLocalizedString("_share_add_sharelink_", comment: ""), + image: utility.loadImage(named: "plus", colors: [NCBrandColor.shared.iconImageColor]) + ) { [self] _ in + shareController.makeNewLinkShare() + } + actions.append(addLinkAction) + } + + // Details action + let detailsAction = UIAction( + title: NSLocalizedString("_details_", comment: ""), + image: utility.loadImage(named: "pencil", colors: [NCBrandColor.shared.iconImageColor]) + ) { [self] _ in + openAdvancePermission(shareController: shareController) + } + actions.append(detailsAction) + + // Unshare action (destructive) + let unshareAction = UIAction( + title: NSLocalizedString("_share_unshare_", comment: ""), + image: utility.loadImage(named: "person.2.slash"), + attributes: .destructive + ) { [self] _ in + Task { + await performUnshare(shareController: shareController) + } } + actions.append(unshareAction) - actions.append( - NCMenuAction( - title: NSLocalizedString("_details_", comment: ""), - icon: utility.loadImage(named: "pencil", colors: [NCBrandColor.shared.iconImageColor]), - accessibilityIdentifier: "shareMenu/details", - sender: sender, - action: { _ in - guard - let advancePermission = UIStoryboard(name: "NCShare", bundle: nil).instantiateViewController(withIdentifier: "NCShareAdvancePermission") as? NCShareAdvancePermission, - let navigationController = self.navigationController, !share.isInvalidated else { return } - advancePermission.networking = self.networking - advancePermission.share = tableShare(value: share) - advancePermission.oldTableShare = tableShare(value: share) - advancePermission.metadata = self.metadata - - if let downloadLimit = try? self.database.getDownloadLimit(byAccount: self.metadata.account, shareToken: share.token) { - advancePermission.downloadLimit = .limited(limit: downloadLimit.limit, count: downloadLimit.count) - } - - navigationController.pushViewController(advancePermission, animated: true) - } - ) - ) - - actions.append( - NCMenuAction( - title: NSLocalizedString("_share_unshare_", comment: ""), - destructive: true, - icon: utility.loadImage(named: "person.2.slash"), - sender: sender, - action: { _ in - Task { - if share.shareType != NKShare.ShareType.publicLink.rawValue, let metadata = self.metadata, metadata.e2eEncrypted && NCGlobal.shared.isE2eeVersion2(capabilities.e2EEApiVersion) { - if await NCNetworkingE2EE().isInUpload(account: metadata.account, serverUrl: metadata.serverUrlFileName) { - let error = NKError(errorCode: NCGlobal.shared.errorE2EEUploadInProgress, errorDescription: NSLocalizedString("_e2e_in_upload_", comment: "")) - return NCContentPresenter().showInfo(error: error) - } - let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: metadata.serverUrlFileName, addUserId: nil, removeUserId: share.shareWith, account: metadata.account) - if error != .success { - return NCContentPresenter().showError(error: error) - } - } - self.networking?.unShare(idShare: share.idShare) - } - } - ) - ) - - self.presentMenu(with: actions, sender: sender) + return UIMenu(title: "", children: actions) } - func toggleQuickPermissionsMenu(isDirectory: Bool, share: tableShare, sender: Any?) { - var actions = [NCMenuAction]() - - actions.append(contentsOf: - [NCMenuAction( - title: NSLocalizedString("_share_read_only_", comment: ""), - icon: utility.loadImage(named: "eye", colors: [NCBrandColor.shared.iconImageColor]), - selected: share.permissions == (NKShare.Permission.read.rawValue + NKShare.Permission.share.rawValue) || share.permissions == NKShare.Permission.read.rawValue, - on: false, - sender: sender, - action: { _ in - let permissions = NCSharePermissions.getPermissionValue(canCreate: false, canEdit: false, canDelete: false, canShare: false, isDirectory: isDirectory) - self.updateSharePermissions(share: share, permissions: permissions) - } - ), - NCMenuAction( - title: NSLocalizedString("_share_editing_", comment: ""), - icon: utility.loadImage(named: "pencil", colors: [NCBrandColor.shared.iconImageColor]), - selected: hasUploadPermission(tableShare: share), - on: false, - sender: sender, - action: { _ in - let permissions = NCSharePermissions.getPermissionValue(canCreate: true, canEdit: true, canDelete: true, canShare: true, isDirectory: isDirectory) - self.updateSharePermissions(share: share, permissions: permissions) - } - ), - NCMenuAction( - title: NSLocalizedString("_custom_permissions_", comment: ""), - icon: utility.loadImage(named: "ellipsis", colors: [NCBrandColor.shared.iconImageColor]), - sender: sender, - action: { _ in - guard - let advancePermission = UIStoryboard(name: "NCShare", bundle: nil).instantiateViewController(withIdentifier: "NCShareAdvancePermission") as? NCShareAdvancePermission, - let navigationController = self.navigationController, !share.isInvalidated else { return } - advancePermission.networking = self.networking - advancePermission.share = tableShare(value: share) - advancePermission.oldTableShare = tableShare(value: share) - advancePermission.metadata = self.metadata - - if let downloadLimit = try? self.database.getDownloadLimit(byAccount: self.metadata.account, shareToken: share.token) { - advancePermission.downloadLimit = .limited(limit: downloadLimit.limit, count: downloadLimit.count) - } - - navigationController.pushViewController(advancePermission, animated: true) - } - )] - ) - - if isDirectory && (share.shareType == NKShare.ShareType.publicLink.rawValue /* public link */ || share.shareType == NKShare.ShareType.email.rawValue) { - actions.insert(NCMenuAction( - title: NSLocalizedString("_share_file_drop_", comment: ""), - icon: utility.loadImage(named: "arrow.up.document", colors: [NCBrandColor.shared.iconImageColor]), - selected: share.permissions == NKShare.Permission.create.rawValue, - on: false, - sender: sender, - action: { _ in - let permissions = NCSharePermissions.getPermissionValue(canRead: false, canCreate: true, canEdit: false, canDelete: false, canShare: false, isDirectory: isDirectory) - self.updateSharePermissions(share: share, permissions: permissions) - } - ), at: 2) + func quickPermissionsMenu() -> UIMenu { + var actions: [UIMenuElement] = [] + + let isReadOnly = share.permissions == (NKShare.Permission.read.rawValue + NKShare.Permission.share.rawValue) || share.permissions == NKShare.Permission.read.rawValue + let isEditing = hasUploadPermission() + let isFileDrop = share.permissions == NKShare.Permission.create.rawValue + + // Read Only + let readOnlyAction = UIAction( + title: NSLocalizedString("_share_read_only_", comment: ""), + image: utility.loadImage(named: "eye", colors: [NCBrandColor.shared.iconImageColor]), + state: isReadOnly ? .on : .off + ) { [self] _ in + let permissions = NCSharePermissions.getPermissionValue(canCreate: false, canEdit: false, canDelete: false, canShare: false, isDirectory: self.isDirectory) + shareController.updateSharePermissions(share: share, permissions: permissions) + } + actions.append(readOnlyAction) + + // Editing + let editingAction = UIAction( + title: NSLocalizedString("_share_editing_", comment: ""), + image: utility.loadImage(named: "pencil", colors: [NCBrandColor.shared.iconImageColor]), + state: isEditing ? .on : .off + ) { [self] _ in + let permissions = NCSharePermissions.getPermissionValue(canCreate: true, canEdit: true, canDelete: true, canShare: true, isDirectory: self.isDirectory) + shareController.updateSharePermissions(share: share, permissions: permissions) + } + actions.append(editingAction) + + // File Drop (only for directories with public link or email share) + if isDirectory && (share.shareType == NKShare.ShareType.publicLink.rawValue || share.shareType == NKShare.ShareType.email.rawValue) { + let fileDropAction = UIAction( + title: NSLocalizedString("_share_file_drop_", comment: ""), + image: utility.loadImage(named: "arrow.up.document", colors: [NCBrandColor.shared.iconImageColor]), + state: isFileDrop ? .on : .off + ) { [self] _ in + let permissions = NCSharePermissions.getPermissionValue(canRead: false, canCreate: true, canEdit: false, canDelete: false, canShare: false, isDirectory: self.isDirectory) + shareController.updateSharePermissions(share: share, permissions: permissions) + } + actions.append(fileDropAction) } - self.presentMenu(with: actions, sender: sender) + // Custom Permissions + let customAction = UIAction( + title: NSLocalizedString("_custom_permissions_", comment: ""), + image: utility.loadImage(named: "ellipsis", colors: [NCBrandColor.shared.iconImageColor]) + ) { [self] _ in + openAdvancePermission(shareController: shareController) + } + actions.append(customAction) + + return UIMenu(title: "", children: actions) } - fileprivate func hasUploadPermission(tableShare: tableShare) -> Bool { + private func hasUploadPermission() -> Bool { let uploadPermissions = [ NCSharePermissions.permissionMaxFileShare, NCSharePermissions.permissionMaxFolderShare, NCSharePermissions.permissionDefaultFileRemoteShareNoSupportShareOption, - NCSharePermissions.permissionDefaultFolderRemoteShareNoSupportShareOption] - return uploadPermissions.contains(tableShare.permissions) + NCSharePermissions.permissionDefaultFolderRemoteShareNoSupportShareOption + ] + return uploadPermissions.contains(share.permissions) + } + + private func openAdvancePermission(shareController: NCShare) { + guard let advancePermission = UIStoryboard(name: "NCShare", bundle: nil).instantiateViewController(withIdentifier: "NCShareAdvancePermission") as? NCShareAdvancePermission, + let navigationController = shareController.navigationController, + !share.isInvalidated, + let metadata = shareController.metadata else { return } + + advancePermission.networking = shareController.networking + advancePermission.share = tableShare(value: share) + advancePermission.oldTableShare = tableShare(value: share) + advancePermission.metadata = metadata + + if let downloadLimit = try? database.getDownloadLimit(byAccount: metadata.account, shareToken: share.token) { + advancePermission.downloadLimit = .limited(limit: downloadLimit.limit, count: downloadLimit.count) + } + + navigationController.pushViewController(advancePermission, animated: true) } + @MainActor + private func performUnshare(shareController: NCShare) async { + let capabilities = NCNetworking.shared.capabilities[share.account] ?? NKCapabilities.Capabilities() + + if share.shareType != NKShare.ShareType.publicLink.rawValue, + let metadata = shareController.metadata, + metadata.e2eEncrypted && NCGlobal.shared.isE2eeVersion2(capabilities.e2EEApiVersion) { + if await NCNetworkingE2EE().isInUpload(account: metadata.account, serverUrl: metadata.serverUrlFileName) { + let error = NKError(errorCode: NCGlobal.shared.errorE2EEUploadInProgress, errorDescription: NSLocalizedString("_e2e_in_upload_", comment: "")) + return NCContentPresenter().showInfo(error: error) + } + let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: metadata.serverUrlFileName, addUserId: nil, removeUserId: share.shareWith, account: metadata.account) + if error != .success { + return NCContentPresenter().showError(error: error) + } + } + shareController.networking?.unShare(idShare: share.idShare) + } +} + +extension NCShare { func updateSharePermissions(share: tableShare, permissions: Int) { let updatedShare = tableShare(value: share) updatedShare.permissions = permissions @@ -178,9 +191,6 @@ extension NCShare { if let model = try database.getDownloadLimit(byAccount: metadata.account, shareToken: updatedShare.token) { downloadLimit = .limited(limit: model.limit, count: model.count) } - if let model = try database.getDownloadLimit(byAccount: metadata.account, shareToken: updatedShare.token) { - downloadLimit = .limited(limit: model.limit, count: model.count) - } } catch { nkLog(error: "Failed to get download limit from database!") return diff --git a/iOSClient/Menu/NCTrash+Menu.swift b/iOSClient/Menu/NCTrash+Menu.swift index b39a432178..9eeb8945c4 100644 --- a/iOSClient/Menu/NCTrash+Menu.swift +++ b/iOSClient/Menu/NCTrash+Menu.swift @@ -29,9 +29,9 @@ import NextcloudKit class NCContextMenuTrash: NSObject { let objectId: String let utility = NCUtility() - weak var trashController: NCTrash? + let trashController: NCTrash - init(objectId: String, trashController: NCTrash?) { + init(objectId: String, trashController: NCTrash) { self.objectId = objectId self.trashController = trashController } @@ -42,10 +42,9 @@ class NCContextMenuTrash: NSObject { let restoreAction = UIAction( title: NSLocalizedString("_restore_", comment: ""), image: utility.loadImage(named: "arrow.counterclockwise", colors: [NCBrandColor.shared.iconImageColor]) - ) { [weak self] _ in - guard let self, let controller = self.trashController else { return } + ) { [self] _ in Task { - await controller.restoreItem(with: self.objectId) + await trashController.restoreItem(with: objectId) } } actions.append(restoreAction) @@ -54,10 +53,9 @@ class NCContextMenuTrash: NSObject { title: NSLocalizedString("_delete_", comment: ""), image: utility.loadImage(named: "trash", colors: [.red]), attributes: .destructive - ) { [weak self] _ in - guard let self, let controller = self.trashController else { return } + ) { [self] _ in Task { - await controller.deleteItems(with: [self.objectId]) + await trashController.deleteItems(with: [objectId]) } } actions.append(deleteAction) diff --git a/iOSClient/Share/NCShare+NCCellDelegate.swift b/iOSClient/Share/NCShare+NCCellDelegate.swift index 76c3bf9dc8..36d47e12bb 100644 --- a/iOSClient/Share/NCShare+NCCellDelegate.swift +++ b/iOSClient/Share/NCShare+NCCellDelegate.swift @@ -47,9 +47,9 @@ extension NCShare: NCShareLinkCellDelegate, NCShareUserCellDelegate { } func tapMenu(with tableShare: tableShare?, sender: Any) { - if let tableShare = tableShare { - self.toggleShareMenu(for: tableShare, sender: sender) - } else { + // Menu is now shown via native context menu on the button + // Only handle the case where there's no tableShare (add new link) + if tableShare == nil { self.makeNewLinkShare() } } @@ -60,7 +60,6 @@ extension NCShare: NCShareLinkCellDelegate, NCShareUserCellDelegate { } func quickStatus(with tableShare: tableShare?, sender: Any) { - guard let tableShare, let metadata else { return } - self.toggleQuickPermissionsMenu(isDirectory: metadata.directory, share: tableShare, sender: sender) + // Menu is now shown via native context menu on the button } } diff --git a/iOSClient/Share/NCShare.swift b/iOSClient/Share/NCShare.swift index f4edbdd304..7ed28b33fb 100644 --- a/iOSClient/Share/NCShare.swift +++ b/iOSClient/Share/NCShare.swift @@ -368,6 +368,12 @@ extension NCShare: UITableViewDataSource { } cell.isDirectory = metadata.directory cell.setupCellUI() + + if cell.tableShare != nil, let tableShare = shares.firstShareLink { + cell.menuButton.menu = NCContextMenuShare(share: tableShare, isDirectory: metadata.isDirectory, canReshare: canReshare, shareController: self).viewMenu() + cell.menuButton.showsMenuAsPrimaryAction = true + } + shareLinksCount += 1 return cell } @@ -383,6 +389,8 @@ extension NCShare: UITableViewDataSource { cell.isDirectory = metadata.directory cell.delegate = self cell.setupCellUI(titleAppendString: String(shareLinksCount)) + cell.menuButton.menu = NCContextMenuShare(share: tableShare, isDirectory: metadata.isDirectory, canReshare: canReshare, shareController: self).viewMenu() + cell.menuButton.showsMenuAsPrimaryAction = true if tableShare.shareType == NKShare.ShareType.publicLink.rawValue { shareLinksCount += 1 } return cell } @@ -395,6 +403,9 @@ extension NCShare: UITableViewDataSource { cell.delegate = self cell.setupCellUI(userId: session.userId, session: session, metadata: metadata) + cell.buttonMenu.menu = NCContextMenuShare(share: tableShare, isDirectory: metadata.isDirectory, canReshare: canReshare, shareController: self).viewMenu() + cell.buttonMenu.showsMenuAsPrimaryAction = true + return cell } } diff --git a/iOSClient/Share/NCShareLinkCell.swift b/iOSClient/Share/NCShareLinkCell.swift index 406dc31b0a..3ef43aa55c 100644 --- a/iOSClient/Share/NCShareLinkCell.swift +++ b/iOSClient/Share/NCShareLinkCell.swift @@ -30,13 +30,14 @@ class NCShareLinkCell: UITableViewCell { @IBOutlet weak var labelQuickStatus: UILabel! @IBOutlet weak var statusStackView: UIStackView! - @IBOutlet private weak var menuButton: UIButton! + @IBOutlet weak var menuButton: UIButton! @IBOutlet private weak var copyButton: UIButton! @IBOutlet weak var imageDownArrow: UIImageView! var tableShare: tableShare? var isDirectory = false weak var delegate: NCShareLinkCellDelegate? +// weak var shareController: NCShare? var isInternalLink = false var indexPath = IndexPath() let utility = NCUtility() @@ -113,6 +114,41 @@ class NCShareLinkCell: UITableViewCell { statusStackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(openQuickStatus))) labelQuickStatus.textColor = NCBrandColor.shared.customer imageDownArrow.image = utility.loadImage(named: "arrowtriangle.down.circle", colors: [NCBrandColor.shared.customer]) + + contentView.bringSubviewToFront(menuButton) + menuButton.menu = nil + menuButton.showsMenuAsPrimaryAction = true + +// contentView.bringSubviewToFront() + +// +// // Configure native context menus +// if let tableShare, let shareController { +// let contextMenu = NCContextMenuShare( +// share: tableShare, +// isDirectory: isDirectory, +// canReshare: shareController.canReshare, +// shareController: shareController +// ) +// menuButton.menu = contextMenu.viewMenu() +// menuButton.showsMenuAsPrimaryAction = true +// +// // Create an invisible button over the status stack for quick permissions menu +// let quickMenuButton = UIButton(type: .system) +// quickMenuButton.translatesAutoresizingMaskIntoConstraints = false +// statusStackView.addSubview(quickMenuButton) +// NSLayoutConstraint.activate([ +// quickMenuButton.leadingAnchor.constraint(equalTo: statusStackView.leadingAnchor), +// quickMenuButton.trailingAnchor.constraint(equalTo: statusStackView.trailingAnchor), +// quickMenuButton.topAnchor.constraint(equalTo: statusStackView.topAnchor), +// quickMenuButton.bottomAnchor.constraint(equalTo: statusStackView.bottomAnchor) +// ]) +// quickMenuButton.menu = contextMenu.quickPermissionsMenu() +// quickMenuButton.showsMenuAsPrimaryAction = true +// } else { +// // For internal link or add new link, keep the old gesture +// statusStackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(openQuickStatus))) +// } } @IBAction func touchUpCopy(_ sender: Any) { @@ -124,7 +160,7 @@ class NCShareLinkCell: UITableViewCell { } @objc func openQuickStatus(_ sender: UITapGestureRecognizer) { - delegate?.quickStatus(with: tableShare, sender: sender) +// delegate?.quickStatus(with: tableShare, sender: sender) } } diff --git a/iOSClient/Share/NCShareUserCell.swift b/iOSClient/Share/NCShareUserCell.swift index e6b60c8a93..654f2a8dbc 100644 --- a/iOSClient/Share/NCShareUserCell.swift +++ b/iOSClient/Share/NCShareUserCell.swift @@ -25,7 +25,6 @@ import DropDown import NextcloudKit class NCShareUserCell: UITableViewCell, NCCellProtocol { - @IBOutlet weak var imageItem: UIImageView! @IBOutlet weak var labelTitle: UILabel! @IBOutlet weak var buttonMenu: UIButton! @@ -41,6 +40,7 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { var isDirectory = false let utility = NCUtility() weak var delegate: NCShareUserCellDelegate? +// weak var shareController: NCShare? var indexPath: IndexPath { get { return index } @@ -119,6 +119,25 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { NCNetworking.shared.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty { NCNetworking.shared.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: tableShare.shareWith, fileName: fileName, account: metadata.account, view: self)) } + + contentView.bringSubviewToFront(buttonMenu) + buttonMenu.menu = nil + buttonMenu.showsMenuAsPrimaryAction = true + +// // Configure native context menus +// if let shareController { +// let contextMenu = NCContextMenuShare( +// share: tableShare, +// isDirectory: isDirectory, +// canReshare: shareController.canReshare, +// shareController: shareController +// ) +// buttonMenu.menu = contextMenu.viewMenu() +// buttonMenu.showsMenuAsPrimaryAction = true +// +// btnQuickStatus.menu = contextMenu.quickPermissionsMenu() +// btnQuickStatus.showsMenuAsPrimaryAction = true +// } } private func getTypeString(_ tableShare: tableShareV2) -> String { diff --git a/iOSClient/Trash/NCTrash+CollectionView.swift b/iOSClient/Trash/NCTrash+CollectionView.swift index 3a5e62546d..694f174be3 100644 --- a/iOSClient/Trash/NCTrash+CollectionView.swift +++ b/iOSClient/Trash/NCTrash+CollectionView.swift @@ -70,18 +70,17 @@ extension NCTrash: UICollectionViewDataSource { cell = gridCell } - if let resultTableTrash = datasource?[indexPath.item] { - let contextMenu = NCContextMenuTrash(objectId: resultTableTrash.fileId, trashController: self) - if let listCell = cell as? NCTrashListCell { - listCell.buttonMore.menu = contextMenu.viewMenu() - listCell.buttonMore.showsMenuAsPrimaryAction = true - } else if let gridCell = cell as? NCTrashGridCell { - gridCell.buttonMore.menu = contextMenu.viewMenu() - gridCell.buttonMore.showsMenuAsPrimaryAction = true - } - } guard let resultTableTrash = datasource?[indexPath.item] else { return cell } + let contextMenu = NCContextMenuTrash(objectId: resultTableTrash.fileId, trashController: self) + if let listCell = cell as? NCTrashListCell { + listCell.buttonMore.menu = contextMenu.viewMenu() + listCell.buttonMore.showsMenuAsPrimaryAction = true + } else if let gridCell = cell as? NCTrashGridCell { + gridCell.buttonMore.menu = contextMenu.viewMenu() + gridCell.buttonMore.showsMenuAsPrimaryAction = true + } + cell.imageItem.contentMode = .scaleAspectFit if resultTableTrash.iconName.isEmpty { From 0c8c310aa249709bd5de72fb2470c7e439b5645a Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 28 Jan 2026 10:43:59 +0100 Subject: [PATCH 05/19] WIP Signed-off-by: Milen Pivchev --- .../Activity/NCActivityTableViewCell.swift | 1 - iOSClient/Menu/NCShare+Menu.swift | 65 +++++++++++++++++++ iOSClient/Menu/NCViewerContextMenu.swift | 1 - iOSClient/Share/NCShare+NCCellDelegate.swift | 3 +- iOSClient/Share/NCShareLinkCell.swift | 2 +- .../NCPlayer/NCPlayerToolBar.swift | 1 - 6 files changed, 68 insertions(+), 5 deletions(-) diff --git a/iOSClient/Activity/NCActivityTableViewCell.swift b/iOSClient/Activity/NCActivityTableViewCell.swift index d94e80ce13..b6bd557345 100644 --- a/iOSClient/Activity/NCActivityTableViewCell.swift +++ b/iOSClient/Activity/NCActivityTableViewCell.swift @@ -5,7 +5,6 @@ import Foundation import UIKit import NextcloudKit -import FloatingPanel import Queuer class NCActivityCollectionViewCell: UICollectionViewCell { diff --git a/iOSClient/Menu/NCShare+Menu.swift b/iOSClient/Menu/NCShare+Menu.swift index c29f609291..4b8b7b55ec 100644 --- a/iOSClient/Menu/NCShare+Menu.swift +++ b/iOSClient/Menu/NCShare+Menu.swift @@ -181,6 +181,71 @@ class NCContextMenuShare: NSObject { } extension NCShare { + func presentQuickStatusActionSheet(for share: tableShare, sender: Any?) { + guard let metadata = metadata else { return } + + let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + let isDirectory = metadata.directory + + // Read Only + let readOnlyAction = UIAlertAction(title: NSLocalizedString("_share_read_only_", comment: ""), style: .default) { [weak self] _ in + let permissions = NCSharePermissions.getPermissionValue(canCreate: false, canEdit: false, canDelete: false, canShare: false, isDirectory: isDirectory) + self?.updateSharePermissions(share: share, permissions: permissions) + } + alertController.addAction(readOnlyAction) + + // Editing + let editingAction = UIAlertAction(title: NSLocalizedString("_share_editing_", comment: ""), style: .default) { [weak self] _ in + let permissions = NCSharePermissions.getPermissionValue(canCreate: true, canEdit: true, canDelete: true, canShare: true, isDirectory: isDirectory) + self?.updateSharePermissions(share: share, permissions: permissions) + } + alertController.addAction(editingAction) + + // File Drop (only for directories with public link or email share) + if isDirectory && (share.shareType == NKShare.ShareType.publicLink.rawValue || share.shareType == NKShare.ShareType.email.rawValue) { + let fileDropAction = UIAlertAction(title: NSLocalizedString("_share_file_drop_", comment: ""), style: .default) { [weak self] _ in + let permissions = NCSharePermissions.getPermissionValue(canRead: false, canCreate: true, canEdit: false, canDelete: false, canShare: false, isDirectory: isDirectory) + self?.updateSharePermissions(share: share, permissions: permissions) + } + alertController.addAction(fileDropAction) + } + + // Custom Permissions + let customAction = UIAlertAction(title: NSLocalizedString("_custom_permissions_", comment: ""), style: .default) { [weak self] _ in + self?.openAdvancePermission(for: share) + } + alertController.addAction(customAction) + + // Cancel + let cancelAction = UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel) + alertController.addAction(cancelAction) + + // iPad popover support + if let popover = alertController.popoverPresentationController, + let sourceView = sender as? UIView { + popover.sourceItem = sourceView + } + + present(alertController, animated: true) + } + + private func openAdvancePermission(for share: tableShare) { + guard let advancePermission = UIStoryboard(name: "NCShare", bundle: nil).instantiateViewController(withIdentifier: "NCShareAdvancePermission") as? NCShareAdvancePermission, + !share.isInvalidated, + let metadata = metadata else { return } + + advancePermission.networking = networking + advancePermission.share = tableShare(value: share) + advancePermission.oldTableShare = tableShare(value: share) + advancePermission.metadata = metadata + + if let downloadLimit = try? NCManageDatabase.shared.getDownloadLimit(byAccount: metadata.account, shareToken: share.token) { + advancePermission.downloadLimit = .limited(limit: downloadLimit.limit, count: downloadLimit.count) + } + + navigationController?.pushViewController(advancePermission, animated: true) + } + func updateSharePermissions(share: tableShare, permissions: Int) { let updatedShare = tableShare(value: share) updatedShare.permissions = permissions diff --git a/iOSClient/Menu/NCViewerContextMenu.swift b/iOSClient/Menu/NCViewerContextMenu.swift index 1b5e13138c..d6880f2137 100644 --- a/iOSClient/Menu/NCViewerContextMenu.swift +++ b/iOSClient/Menu/NCViewerContextMenu.swift @@ -3,7 +3,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later import UIKit -import FloatingPanel import NextcloudKit /** diff --git a/iOSClient/Share/NCShare+NCCellDelegate.swift b/iOSClient/Share/NCShare+NCCellDelegate.swift index 36d47e12bb..69a0437366 100644 --- a/iOSClient/Share/NCShare+NCCellDelegate.swift +++ b/iOSClient/Share/NCShare+NCCellDelegate.swift @@ -60,6 +60,7 @@ extension NCShare: NCShareLinkCellDelegate, NCShareUserCellDelegate { } func quickStatus(with tableShare: tableShare?, sender: Any) { - // Menu is now shown via native context menu on the button + guard let tableShare else { return } + presentQuickStatusActionSheet(for: tableShare, sender: sender) } } diff --git a/iOSClient/Share/NCShareLinkCell.swift b/iOSClient/Share/NCShareLinkCell.swift index 3ef43aa55c..2dfe2e59c5 100644 --- a/iOSClient/Share/NCShareLinkCell.swift +++ b/iOSClient/Share/NCShareLinkCell.swift @@ -160,7 +160,7 @@ class NCShareLinkCell: UITableViewCell { } @objc func openQuickStatus(_ sender: UITapGestureRecognizer) { -// delegate?.quickStatus(with: tableShare, sender: sender) + delegate?.quickStatus(with: tableShare, sender: sender) } } diff --git a/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift b/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift index 52febd51ac..6d58c454dd 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift @@ -9,7 +9,6 @@ import UIKit import AVKit import MediaPlayer import MobileVLCKit -import FloatingPanel import Alamofire import LucidBanner From bcdacc5bd050d82cba9517567c21eade13319f43 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 28 Jan 2026 14:13:53 +0100 Subject: [PATCH 06/19] WIP Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCShare+Menu.swift | 83 +------------------------- iOSClient/Share/NCShare.swift | 84 +++++++++++++++++++++++++++ iOSClient/Share/NCShareLinkCell.swift | 2 +- 3 files changed, 86 insertions(+), 83 deletions(-) diff --git a/iOSClient/Menu/NCShare+Menu.swift b/iOSClient/Menu/NCShare+Menu.swift index 4b8b7b55ec..9996287319 100644 --- a/iOSClient/Menu/NCShare+Menu.swift +++ b/iOSClient/Menu/NCShare+Menu.swift @@ -181,86 +181,5 @@ class NCContextMenuShare: NSObject { } extension NCShare { - func presentQuickStatusActionSheet(for share: tableShare, sender: Any?) { - guard let metadata = metadata else { return } - - let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - let isDirectory = metadata.directory - - // Read Only - let readOnlyAction = UIAlertAction(title: NSLocalizedString("_share_read_only_", comment: ""), style: .default) { [weak self] _ in - let permissions = NCSharePermissions.getPermissionValue(canCreate: false, canEdit: false, canDelete: false, canShare: false, isDirectory: isDirectory) - self?.updateSharePermissions(share: share, permissions: permissions) - } - alertController.addAction(readOnlyAction) - - // Editing - let editingAction = UIAlertAction(title: NSLocalizedString("_share_editing_", comment: ""), style: .default) { [weak self] _ in - let permissions = NCSharePermissions.getPermissionValue(canCreate: true, canEdit: true, canDelete: true, canShare: true, isDirectory: isDirectory) - self?.updateSharePermissions(share: share, permissions: permissions) - } - alertController.addAction(editingAction) - - // File Drop (only for directories with public link or email share) - if isDirectory && (share.shareType == NKShare.ShareType.publicLink.rawValue || share.shareType == NKShare.ShareType.email.rawValue) { - let fileDropAction = UIAlertAction(title: NSLocalizedString("_share_file_drop_", comment: ""), style: .default) { [weak self] _ in - let permissions = NCSharePermissions.getPermissionValue(canRead: false, canCreate: true, canEdit: false, canDelete: false, canShare: false, isDirectory: isDirectory) - self?.updateSharePermissions(share: share, permissions: permissions) - } - alertController.addAction(fileDropAction) - } - - // Custom Permissions - let customAction = UIAlertAction(title: NSLocalizedString("_custom_permissions_", comment: ""), style: .default) { [weak self] _ in - self?.openAdvancePermission(for: share) - } - alertController.addAction(customAction) - - // Cancel - let cancelAction = UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel) - alertController.addAction(cancelAction) - - // iPad popover support - if let popover = alertController.popoverPresentationController, - let sourceView = sender as? UIView { - popover.sourceItem = sourceView - } - - present(alertController, animated: true) - } - - private func openAdvancePermission(for share: tableShare) { - guard let advancePermission = UIStoryboard(name: "NCShare", bundle: nil).instantiateViewController(withIdentifier: "NCShareAdvancePermission") as? NCShareAdvancePermission, - !share.isInvalidated, - let metadata = metadata else { return } - - advancePermission.networking = networking - advancePermission.share = tableShare(value: share) - advancePermission.oldTableShare = tableShare(value: share) - advancePermission.metadata = metadata - - if let downloadLimit = try? NCManageDatabase.shared.getDownloadLimit(byAccount: metadata.account, shareToken: share.token) { - advancePermission.downloadLimit = .limited(limit: downloadLimit.limit, count: downloadLimit.count) - } - - navigationController?.pushViewController(advancePermission, animated: true) - } - - func updateSharePermissions(share: tableShare, permissions: Int) { - let updatedShare = tableShare(value: share) - updatedShare.permissions = permissions - - var downloadLimit: DownloadLimitViewModel = .unlimited - - do { - if let model = try database.getDownloadLimit(byAccount: metadata.account, shareToken: updatedShare.token) { - downloadLimit = .limited(limit: model.limit, count: model.count) - } - } catch { - nkLog(error: "Failed to get download limit from database!") - return - } - - networking?.updateShare(updatedShare, downloadLimit: downloadLimit) - } + } diff --git a/iOSClient/Share/NCShare.swift b/iOSClient/Share/NCShare.swift index 7ed28b33fb..f9fdc29479 100644 --- a/iOSClient/Share/NCShare.swift +++ b/iOSClient/Share/NCShare.swift @@ -221,6 +221,90 @@ class NCShare: UIViewController, NCSharePagingContent { cnPicker.predicateForSelectionOfProperty = NSPredicate(format: "emailAddresses.@count > 0") self.present(cnPicker, animated: true) } + + func presentQuickStatusActionSheet(for share: tableShare, sender: Any?) { + guard let metadata = metadata else { return } + + let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + let isDirectory = metadata.directory + + // Read Only + let readOnlyAction = UIAlertAction(title: NSLocalizedString("_share_read_only_", comment: ""), style: .default) { [weak self] _ in + let permissions = NCSharePermissions.getPermissionValue(canCreate: false, canEdit: false, canDelete: false, canShare: false, isDirectory: isDirectory) + self?.updateSharePermissions(share: share, permissions: permissions) + } + alertController.addAction(readOnlyAction) + + // Editing + let editingAction = UIAlertAction(title: NSLocalizedString("_share_editing_", comment: ""), style: .default) { [weak self] _ in + let permissions = NCSharePermissions.getPermissionValue(canCreate: true, canEdit: true, canDelete: true, canShare: true, isDirectory: isDirectory) + self?.updateSharePermissions(share: share, permissions: permissions) + } + alertController.addAction(editingAction) + + // File Drop (only for directories with public link or email share) + if isDirectory && (share.shareType == NKShare.ShareType.publicLink.rawValue || share.shareType == NKShare.ShareType.email.rawValue) { + let fileDropAction = UIAlertAction(title: NSLocalizedString("_share_file_drop_", comment: ""), style: .default) { [weak self] _ in + let permissions = NCSharePermissions.getPermissionValue(canRead: false, canCreate: true, canEdit: false, canDelete: false, canShare: false, isDirectory: isDirectory) + self?.updateSharePermissions(share: share, permissions: permissions) + } + alertController.addAction(fileDropAction) + } + + // Custom Permissions + let customAction = UIAlertAction(title: NSLocalizedString("_custom_permissions_", comment: ""), style: .default) { [weak self] _ in + self?.openAdvancePermission(for: share) + } + alertController.addAction(customAction) + + // Cancel + let cancelAction = UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel) + alertController.addAction(cancelAction) + + // iPad popover support + if let popover = alertController.popoverPresentationController, + let sourceView = sender as? UIView { + let barItem = UIBarButtonItem(customView: sourceView) + popover.sourceItem = barItem + } + + present(alertController, animated: true) + } + + private func openAdvancePermission(for share: tableShare) { + guard let advancePermission = UIStoryboard(name: "NCShare", bundle: nil).instantiateViewController(withIdentifier: "NCShareAdvancePermission") as? NCShareAdvancePermission, + !share.isInvalidated, + let metadata = metadata else { return } + + advancePermission.networking = networking + advancePermission.share = tableShare(value: share) + advancePermission.oldTableShare = tableShare(value: share) + advancePermission.metadata = metadata + + if let downloadLimit = try? NCManageDatabase.shared.getDownloadLimit(byAccount: metadata.account, shareToken: share.token) { + advancePermission.downloadLimit = .limited(limit: downloadLimit.limit, count: downloadLimit.count) + } + + navigationController?.pushViewController(advancePermission, animated: true) + } + + func updateSharePermissions(share: tableShare, permissions: Int) { + let updatedShare = tableShare(value: share) + updatedShare.permissions = permissions + + var downloadLimit: DownloadLimitViewModel = .unlimited + + do { + if let model = try database.getDownloadLimit(byAccount: metadata.account, shareToken: updatedShare.token) { + downloadLimit = .limited(limit: model.limit, count: model.count) + } + } catch { + nkLog(error: "Failed to get download limit from database!") + return + } + + networking?.updateShare(updatedShare, downloadLimit: downloadLimit) + } } // MARK: - NCShareNetworkingDelegate diff --git a/iOSClient/Share/NCShareLinkCell.swift b/iOSClient/Share/NCShareLinkCell.swift index 2dfe2e59c5..4319a1fbb9 100644 --- a/iOSClient/Share/NCShareLinkCell.swift +++ b/iOSClient/Share/NCShareLinkCell.swift @@ -160,7 +160,7 @@ class NCShareLinkCell: UITableViewCell { } @objc func openQuickStatus(_ sender: UITapGestureRecognizer) { - delegate?.quickStatus(with: tableShare, sender: sender) + delegate?.quickStatus(with: tableShare, sender: sender.view ?? sender) } } From 77c274a1a76ef6cb85215ddd8908296d76b602bd Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 28 Jan 2026 14:46:06 +0100 Subject: [PATCH 07/19] Refactor Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 48 +++++++++---------- ....swift => NCContextMenuNotification.swift} | 0 ...re+Menu.swift => NCContextMenuShare.swift} | 4 -- ...sh+Menu.swift => NCContextMenuTrash.swift} | 0 4 files changed, 24 insertions(+), 28 deletions(-) rename iOSClient/Menu/{NCNotification+Menu.swift => NCContextMenuNotification.swift} (100%) rename iOSClient/Menu/{NCShare+Menu.swift => NCContextMenuShare.swift} (99%) rename iOSClient/Menu/{NCTrash+Menu.swift => NCContextMenuTrash.swift} (100%) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index d0a1957839..877620670f 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -11,9 +11,7 @@ 2C1D5D7923E2DE9100334ABB /* NCBrand.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76B3CCD1EAE01BD00921AC9 /* NCBrand.swift */; }; 2C33C48223E2C475005F963B /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C33C48123E2C475005F963B /* NotificationService.swift */; }; 2C33C48623E2C475005F963B /* Notification Service Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2C33C47F23E2C475005F963B /* Notification Service Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 3704EB2A23D5A58400455C5B /* NCMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3704EB2923D5A58400455C5B /* NCMenu.storyboard */; }; 370D26AF248A3D7A00121797 /* NCCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370D26AE248A3D7A00121797 /* NCCellProtocol.swift */; }; - 371B5A2E23D0B04500FAFAE9 /* NCMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B5A2D23D0B04500FAFAE9 /* NCMenu.swift */; }; 8491B1CD273BBA82001C8C5B /* UIViewController+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8491B1CC273BBA82001C8C5B /* UIViewController+Menu.swift */; }; AA3C85E82D36B08C00F74F12 /* UITestBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3C85E72D36B08C00F74F12 /* UITestBackend.swift */; }; AA3C85EB2D36BBFB00F74F12 /* OCSResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3C85EA2D36BBF400F74F12 /* OCSResponse.swift */; }; @@ -71,7 +69,7 @@ AF730AF827834B1400B7520E /* NCShare+NCCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF730AF727834B1400B7520E /* NCShare+NCCellDelegate.swift */; }; AF730AFA27843E4C00B7520E /* NCShareExtension+NCAccountRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF730AF927843E4C00B7520E /* NCShareExtension+NCAccountRequestDelegate.swift */; }; AF7E504E27A2D8FF00B5E4AF /* UIBarButton+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF7E504D27A2D8FF00B5E4AF /* UIBarButton+Extension.swift */; }; - AF93471227E2341B002537EE /* NCShare+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF93471127E2341B002537EE /* NCShare+Menu.swift */; }; + AF93471227E2341B002537EE /* NCContextMenuShare.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF93471127E2341B002537EE /* NCContextMenuShare.swift */; }; AF93471927E2361E002537EE /* NCShareAdvancePermissionFooter.xib in Resources */ = {isa = PBXBuildFile; fileRef = AF93471427E2361E002537EE /* NCShareAdvancePermissionFooter.xib */; }; AF93471A27E2361E002537EE /* NCShareHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF93471527E2361E002537EE /* NCShareHeader.swift */; }; AF93471B27E2361E002537EE /* NCShareAdvancePermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF93471627E2361E002537EE /* NCShareAdvancePermission.swift */; }; @@ -79,7 +77,6 @@ AF93471D27E2361E002537EE /* NCShareAdvancePermissionFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF93471827E2361E002537EE /* NCShareAdvancePermissionFooter.swift */; }; AF93474C27E34120002537EE /* NCUtility+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF93474B27E34120002537EE /* NCUtility+Image.swift */; }; AF93474E27E3F212002537EE /* NCShareNewUserAddComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF93474D27E3F211002537EE /* NCShareNewUserAddComment.swift */; }; - AF935067276B84E700BD078F /* NCMenu+FloatingPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF935066276B84E700BD078F /* NCMenu+FloatingPanel.swift */; }; AFA2AC8527849604008E1EA7 /* NCActivityCommentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA2AC8427849604008E1EA7 /* NCActivityCommentView.swift */; }; AFCE353327E4ED1900FEA6C2 /* UIToolbar+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353227E4ED1900FEA6C2 /* UIToolbar+Extension.swift */; }; AFCE353527E4ED5900FEA6C2 /* DateFormatter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */; }; @@ -166,6 +163,9 @@ F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */; }; F3C587AE2D47E4FE004532DB /* PHAssetCollectionThumbnailLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C587AD2D47E4FE004532DB /* PHAssetCollectionThumbnailLoader.swift */; }; F3CA337D2D0B2B6C00672333 /* AlbumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3CA337C2D0B2B6A00672333 /* AlbumModel.swift */; }; + F3DDB2082F2A4A8E008A9B32 /* NCMenu+FloatingPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DDB2072F2A4A8E008A9B32 /* NCMenu+FloatingPanel.swift */; }; + F3DDB2092F2A4A8E008A9B32 /* NCMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DDB2062F2A4A8E008A9B32 /* NCMenu.swift */; }; + F3DDB20A2F2A4A8E008A9B32 /* NCMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F3DDB2052F2A4A8E008A9B32 /* NCMenu.storyboard */; }; F3E173B02C9AF637006D177A /* ScreenAwakeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E173AF2C9AF637006D177A /* ScreenAwakeManager.swift */; }; F3E173C02C9B1067006D177A /* AwakeMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E173BF2C9B1067006D177A /* AwakeMode.swift */; }; F3E173C12C9B1067006D177A /* AwakeMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E173BF2C9B1067006D177A /* AwakeMode.swift */; }; @@ -421,7 +421,7 @@ F75A9EE723796C6F0044CFCE /* NCNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75A9EE523796C6F0044CFCE /* NCNetworking.swift */; }; F75C0C4823D1FAE300163CC8 /* NCRichWorkspaceCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75C0C4723D1FAE300163CC8 /* NCRichWorkspaceCommon.swift */; }; F75CA1472962F13700B01130 /* NCHUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75CA1462962F13700B01130 /* NCHUDView.swift */; }; - F75D19E325EFE09000D74598 /* NCTrash+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75D19E225EFE09000D74598 /* NCTrash+Menu.swift */; }; + F75D19E325EFE09000D74598 /* NCContextMenuTrash.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75D19E225EFE09000D74598 /* NCContextMenuTrash.swift */; }; F75D901F2D2BE12E003E740B /* NCRecommendationsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F75D901E2D2BE12E003E740B /* NCRecommendationsCell.xib */; }; F75D90212D2BE26F003E740B /* NCRecommendationsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75D90202D2BE26C003E740B /* NCRecommendationsCell.swift */; }; F75DD765290ABB25002EB562 /* Intent.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = F75DD769290ABB25002EB562 /* Intent.intentdefinition */; }; @@ -910,7 +910,7 @@ F7FA7FFC2C0F4EE40072FC60 /* NCViewerQuickLookView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7FA7FFB2C0F4EE40072FC60 /* NCViewerQuickLookView.swift */; }; F7FA80002C0F4F3B0072FC60 /* NCUploadAssetsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7FA7FFE2C0F4F3B0072FC60 /* NCUploadAssetsModel.swift */; }; F7FA80012C0F4F3B0072FC60 /* NCUploadAssetsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7FA7FFF2C0F4F3B0072FC60 /* NCUploadAssetsView.swift */; }; - F7FAFD3A28BFA948000777FE /* NCNotification+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7FAFD3928BFA947000777FE /* NCNotification+Menu.swift */; }; + F7FAFD3A28BFA948000777FE /* NCContextMenuNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7FAFD3928BFA947000777FE /* NCContextMenuNotification.swift */; }; F7FDFF692E437E55000D7688 /* NCAccountRequest.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7FDFF512E437E55000D7688 /* NCAccountRequest.storyboard */; }; F7FDFF6A2E437E55000D7688 /* NCShareAccounts.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7FDFF532E437E55000D7688 /* NCShareAccounts.storyboard */; }; F7FDFF6B2E437E55000D7688 /* NCAccountRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7FDFF522E437E55000D7688 /* NCAccountRequest.swift */; }; @@ -1113,9 +1113,7 @@ 2C33C47F23E2C475005F963B /* Notification Service Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Notification Service Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 2C33C48123E2C475005F963B /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; 2C33C48A23E2CC26005F963B /* Notification_Service_Extension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Notification_Service_Extension-Bridging-Header.h"; sourceTree = ""; }; - 3704EB2923D5A58400455C5B /* NCMenu.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCMenu.storyboard; sourceTree = ""; }; 370D26AE248A3D7A00121797 /* NCCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCellProtocol.swift; sourceTree = ""; }; - 371B5A2D23D0B04500FAFAE9 /* NCMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMenu.swift; sourceTree = ""; }; 8491B1CC273BBA82001C8C5B /* UIViewController+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Menu.swift"; sourceTree = ""; }; AA3C85E72D36B08C00F74F12 /* UITestBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestBackend.swift; sourceTree = ""; }; AA3C85EA2D36BBF400F74F12 /* OCSResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCSResponse.swift; sourceTree = ""; }; @@ -1220,7 +1218,7 @@ AF730AF927843E4C00B7520E /* NCShareExtension+NCAccountRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShareExtension+NCAccountRequestDelegate.swift"; sourceTree = ""; }; AF7E504D27A2D8FF00B5E4AF /* UIBarButton+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBarButton+Extension.swift"; sourceTree = ""; }; AF8ED1F92757821000B8DBC4 /* NextcloudUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - AF93471127E2341B002537EE /* NCShare+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShare+Menu.swift"; sourceTree = ""; }; + AF93471127E2341B002537EE /* NCContextMenuShare.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuShare.swift; sourceTree = ""; }; AF93471427E2361E002537EE /* NCShareAdvancePermissionFooter.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCShareAdvancePermissionFooter.xib; sourceTree = ""; }; AF93471527E2361E002537EE /* NCShareHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCShareHeader.swift; sourceTree = ""; }; AF93471627E2361E002537EE /* NCShareAdvancePermission.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCShareAdvancePermission.swift; sourceTree = ""; }; @@ -1228,7 +1226,6 @@ AF93471827E2361E002537EE /* NCShareAdvancePermissionFooter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCShareAdvancePermissionFooter.swift; sourceTree = ""; }; AF93474B27E34120002537EE /* NCUtility+Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCUtility+Image.swift"; sourceTree = ""; }; AF93474D27E3F211002537EE /* NCShareNewUserAddComment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCShareNewUserAddComment.swift; sourceTree = ""; }; - AF935066276B84E700BD078F /* NCMenu+FloatingPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCMenu+FloatingPanel.swift"; sourceTree = ""; }; AFA2AC8427849604008E1EA7 /* NCActivityCommentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityCommentView.swift; sourceTree = ""; }; AFCE353227E4ED1900FEA6C2 /* UIToolbar+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIToolbar+Extension.swift"; sourceTree = ""; }; AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extension.swift"; sourceTree = ""; }; @@ -1277,6 +1274,9 @@ F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CCCellMore.swift; sourceTree = ""; }; F3C587AD2D47E4FE004532DB /* PHAssetCollectionThumbnailLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHAssetCollectionThumbnailLoader.swift; sourceTree = ""; }; F3CA337C2D0B2B6A00672333 /* AlbumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumModel.swift; sourceTree = ""; }; + F3DDB2052F2A4A8E008A9B32 /* NCMenu.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCMenu.storyboard; sourceTree = ""; }; + F3DDB2062F2A4A8E008A9B32 /* NCMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMenu.swift; sourceTree = ""; }; + F3DDB2072F2A4A8E008A9B32 /* NCMenu+FloatingPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCMenu+FloatingPanel.swift"; sourceTree = ""; }; F3E173AF2C9AF637006D177A /* ScreenAwakeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenAwakeManager.swift; sourceTree = ""; }; F3E173BF2C9B1067006D177A /* AwakeMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AwakeMode.swift; sourceTree = ""; }; F3F442ED2DDE292600FD701F /* NCMetadataPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMetadataPermissions.swift; sourceTree = ""; }; @@ -1421,7 +1421,7 @@ F75B923D1ECAE55E00199C96 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; F75C0C4723D1FAE300163CC8 /* NCRichWorkspaceCommon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCRichWorkspaceCommon.swift; sourceTree = ""; }; F75CA1462962F13700B01130 /* NCHUDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCHUDView.swift; sourceTree = ""; }; - F75D19E225EFE09000D74598 /* NCTrash+Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCTrash+Menu.swift"; sourceTree = ""; }; + F75D19E225EFE09000D74598 /* NCContextMenuTrash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCContextMenuTrash.swift; sourceTree = ""; }; F75D901E2D2BE12E003E740B /* NCRecommendationsCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCRecommendationsCell.xib; sourceTree = ""; }; F75D90202D2BE26C003E740B /* NCRecommendationsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCRecommendationsCell.swift; sourceTree = ""; }; F75DD768290ABB25002EB562 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intent.intentdefinition; sourceTree = ""; }; @@ -1800,7 +1800,7 @@ F7FA7FFB2C0F4EE40072FC60 /* NCViewerQuickLookView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCViewerQuickLookView.swift; sourceTree = ""; }; F7FA7FFE2C0F4F3B0072FC60 /* NCUploadAssetsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCUploadAssetsModel.swift; sourceTree = ""; }; F7FA7FFF2C0F4F3B0072FC60 /* NCUploadAssetsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCUploadAssetsView.swift; sourceTree = ""; }; - F7FAFD3928BFA947000777FE /* NCNotification+Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCNotification+Menu.swift"; sourceTree = ""; }; + F7FAFD3928BFA947000777FE /* NCContextMenuNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCContextMenuNotification.swift; sourceTree = ""; }; F7FDFF512E437E55000D7688 /* NCAccountRequest.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCAccountRequest.storyboard; sourceTree = ""; }; F7FDFF522E437E55000D7688 /* NCAccountRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAccountRequest.swift; sourceTree = ""; }; F7FDFF532E437E55000D7688 /* NCShareAccounts.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCShareAccounts.storyboard; sourceTree = ""; }; @@ -2000,15 +2000,15 @@ 371B5A2F23D0B04B00FAFAE9 /* Menu */ = { isa = PBXGroup; children = ( + F3DDB2052F2A4A8E008A9B32 /* NCMenu.storyboard */, + F3DDB2062F2A4A8E008A9B32 /* NCMenu.swift */, + F3DDB2072F2A4A8E008A9B32 /* NCMenu+FloatingPanel.swift */, F376A3732E5CC5FF0067EE25 /* ContextMenuActions.swift */, F78C6FDD296D677300C952C3 /* NCContextMenuMain.swift */, - 3704EB2923D5A58400455C5B /* NCMenu.storyboard */, - 371B5A2D23D0B04500FAFAE9 /* NCMenu.swift */, - AF935066276B84E700BD078F /* NCMenu+FloatingPanel.swift */, AF68326927BE65A90010BF0B /* NCMenuAction.swift */, - F7FAFD3928BFA947000777FE /* NCNotification+Menu.swift */, - AF93471127E2341B002537EE /* NCShare+Menu.swift */, - F75D19E225EFE09000D74598 /* NCTrash+Menu.swift */, + F7FAFD3928BFA947000777FE /* NCContextMenuNotification.swift */, + AF93471127E2341B002537EE /* NCContextMenuShare.swift */, + F75D19E225EFE09000D74598 /* NCContextMenuTrash.swift */, F710D2012405826100A6033D /* NCViewerContextMenu.swift */, 8491B1CC273BBA82001C8C5B /* UIViewController+Menu.swift */, ); @@ -3967,7 +3967,6 @@ F7F4F10B27ECDBDB008676F9 /* Inconsolata-Light.ttf in Resources */, F7EF2AEC2E43157B0081B2C9 /* NCNotification.storyboard in Resources */, F7CEE6002BA9A5C9003EFD89 /* NCTrashGridCell.xib in Resources */, - 3704EB2A23D5A58400455C5B /* NCMenu.storyboard in Resources */, AF93471C27E2361E002537EE /* NCShareHeader.xib in Resources */, F75D901F2D2BE12E003E740B /* NCRecommendationsCell.xib in Resources */, F7F4F10527ECDBDB008676F9 /* Inconsolata-SemiBold.ttf in Resources */, @@ -4016,6 +4015,7 @@ F7AE00FA230E81EB007ACF8A /* NCBrowserWeb.storyboard in Resources */, F7EDE514262DC2CD00414FE6 /* NCSelectCommandViewSelect+CreateFolder.xib in Resources */, F7B398422A6A91D5007538D6 /* NCSectionFirstHeader.xib in Resources */, + F3DDB20A2F2A4A8E008A9B32 /* NCMenu.storyboard in Resources */, F7501C322212E57500FB1415 /* NCMedia.storyboard in Resources */, F76687082B7D067400779E3F /* NCAudioRecorderViewController.storyboard in Resources */, ); @@ -4438,6 +4438,8 @@ F733598125C1C188002ABA72 /* NCAskAuthorization.swift in Sources */, 370D26AF248A3D7A00121797 /* NCCellProtocol.swift in Sources */, F32FADA92D1176E3007035E2 /* UIButton+Extension.swift in Sources */, + F3DDB2082F2A4A8E008A9B32 /* NCMenu+FloatingPanel.swift in Sources */, + F3DDB2092F2A4A8E008A9B32 /* NCMenu.swift in Sources */, F7DF7B3F2F1A2EF900514020 /* BannerView.swift in Sources */, F768822C2C0DD1E7001CF441 /* NCPreferences.swift in Sources */, F7CAFE1D2F17A35F00DB35A5 /* NCNetworking+Actor.swift in Sources */, @@ -4454,7 +4456,6 @@ F718E25A2DF2D5D1004038AF /* NCBackgroundLocationUploadManager.swift in Sources */, F761856B29E98543006EB3B0 /* NCIntroViewController.swift in Sources */, F7743A142C33F13A0034F670 /* NCCollectionViewCommon+CollectionViewDataSource.swift in Sources */, - AF935067276B84E700BD078F /* NCMenu+FloatingPanel.swift in Sources */, F321DA8A2B71205A00DDA0E6 /* NCTrashSelectTabBar.swift in Sources */, F76882282C0DD1E7001CF441 /* NCEndToEndInitialize.swift in Sources */, F702F2CD25EE5B4F008F8E80 /* AppDelegate.swift in Sources */, @@ -4476,7 +4477,6 @@ AF93471D27E2361E002537EE /* NCShareAdvancePermissionFooter.swift in Sources */, AF1A9B6427D0CA1E00F17A9E /* UIAlertController+Extension.swift in Sources */, F7FA80012C0F4F3B0072FC60 /* NCUploadAssetsView.swift in Sources */, - 371B5A2E23D0B04500FAFAE9 /* NCMenu.swift in Sources */, F74230F32C79B57200CA1ACA /* NCNetworking+Task.swift in Sources */, F757CC8229E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */, F7B769A82B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */, @@ -4652,7 +4652,7 @@ F72FD3B5297ED49A00075D28 /* NCManageDatabase+E2EE.swift in Sources */, F7BD0A022C4689A4003A4A6D /* NCMedia+CollectionViewDelegate.swift in Sources */, F3A047982BD2668800658E7B /* NCAssistantCreateNewTask.swift in Sources */, - AF93471227E2341B002537EE /* NCShare+Menu.swift in Sources */, + AF93471227E2341B002537EE /* NCContextMenuShare.swift in Sources */, F7EFA47825ADBA500083159A /* NCViewerProviderContextMenu.swift in Sources */, F755BD9B20594AC7008C5FBB /* NCService.swift in Sources */, F376A3742E5CC6030067EE25 /* ContextMenuActions.swift in Sources */, @@ -4666,7 +4666,7 @@ F76882322C0DD1E7001CF441 /* NCAutoUploadView.swift in Sources */, F36E64F72B9245210085ABB5 /* NCCollectionViewCommon+SelectTabBarDelegate.swift in Sources */, F79A65C62191D95E00FF6DCC /* NCSelect.swift in Sources */, - F75D19E325EFE09000D74598 /* NCTrash+Menu.swift in Sources */, + F75D19E325EFE09000D74598 /* NCContextMenuTrash.swift in Sources */, F34E1ADB2ECC842B00FA10C3 /* NCStatusMessageModel.swift in Sources */, F70CAE3A1F8CF31A008125FD /* NCEndToEndEncryption.m in Sources */, AA8D316E2D4123B200FE2775 /* NCShareDownloadLimitTableViewControllerDelegate.swift in Sources */, @@ -4684,7 +4684,7 @@ F70753EB2542A99800972D44 /* NCViewerMediaPage.swift in Sources */, F7817CF829801A3500FFBC65 /* Data+Extension.swift in Sources */, F749B651297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */, - F7FAFD3A28BFA948000777FE /* NCNotification+Menu.swift in Sources */, + F7FAFD3A28BFA948000777FE /* NCContextMenuNotification.swift in Sources */, F74C0436253F1CDC009762AB /* NCShares.swift in Sources */, F79699E72E689F68000EC82A /* NCMediaNavigationController.swift in Sources */, F7AC1CB028AB94490032D99F /* Array+Extension.swift in Sources */, diff --git a/iOSClient/Menu/NCNotification+Menu.swift b/iOSClient/Menu/NCContextMenuNotification.swift similarity index 100% rename from iOSClient/Menu/NCNotification+Menu.swift rename to iOSClient/Menu/NCContextMenuNotification.swift diff --git a/iOSClient/Menu/NCShare+Menu.swift b/iOSClient/Menu/NCContextMenuShare.swift similarity index 99% rename from iOSClient/Menu/NCShare+Menu.swift rename to iOSClient/Menu/NCContextMenuShare.swift index 9996287319..364cffc322 100644 --- a/iOSClient/Menu/NCShare+Menu.swift +++ b/iOSClient/Menu/NCContextMenuShare.swift @@ -179,7 +179,3 @@ class NCContextMenuShare: NSObject { shareController.networking?.unShare(idShare: share.idShare) } } - -extension NCShare { - -} diff --git a/iOSClient/Menu/NCTrash+Menu.swift b/iOSClient/Menu/NCContextMenuTrash.swift similarity index 100% rename from iOSClient/Menu/NCTrash+Menu.swift rename to iOSClient/Menu/NCContextMenuTrash.swift From f9036c5b65007c60eff6987aff1c1894f8355c08 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 2 Feb 2026 12:01:21 +0100 Subject: [PATCH 08/19] Refactor Signed-off-by: Milen Pivchev --- .../Menu/NCContextMenuNotification.swift | 5 +- iOSClient/Notification/NCNotification.swift | 10 --- iOSClient/Share/NCShareLinkCell.xib | 29 +++--- iOSClient/Share/NCShareUserCell.swift | 14 +-- iOSClient/Share/NCShareUserCell.xib | 90 +++++++++---------- 5 files changed, 66 insertions(+), 82 deletions(-) diff --git a/iOSClient/Menu/NCContextMenuNotification.swift b/iOSClient/Menu/NCContextMenuNotification.swift index 9437c80109..52efcc5d12 100644 --- a/iOSClient/Menu/NCContextMenuNotification.swift +++ b/iOSClient/Menu/NCContextMenuNotification.swift @@ -42,9 +42,8 @@ class NCContextMenuNotification: NSObject { for action in jsonActions { let label = action["label"].stringValue actions.append( - UIAction(title: label, image: nil) { [weak self] _ in - guard let self else { return } - self.delegate?.tapAction(with: self.notification, label: label, sender: nil) + UIAction(title: label, image: nil) { [self] _ in + delegate?.tapAction(with: notification, label: label, sender: nil) } ) } diff --git a/iOSClient/Notification/NCNotification.swift b/iOSClient/Notification/NCNotification.swift index 9588499840..c1cd0be3e2 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -316,10 +316,6 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { } } - func tapMore(with notification: NKNotifications, sender: Any?) { -// toggleMenu(notification: notification, sender: sender) - } - // MARK: - Load notification networking @MainActor @@ -410,15 +406,9 @@ class NCNotificationCell: UITableViewCell, NCCellProtocol { else { return } delegate?.tapAction(with: notification, label: label, sender: sender) } - - @IBAction func touchUpInsideMore(_ sender: Any) { - guard let notification = notification else { return } - delegate?.tapMore(with: notification, sender: sender) - } } protocol NCNotificationCellDelegate: AnyObject { func tapRemove(with notification: NKNotifications, sender: Any?) func tapAction(with notification: NKNotifications, label: String, sender: Any?) - func tapMore(with notification: NKNotifications, sender: Any?) } diff --git a/iOSClient/Share/NCShareLinkCell.xib b/iOSClient/Share/NCShareLinkCell.xib index d5810a5294..3311593091 100755 --- a/iOSClient/Share/NCShareLinkCell.xib +++ b/iOSClient/Share/NCShareLinkCell.xib @@ -1,9 +1,8 @@ - + - - + @@ -19,7 +18,7 @@ - + @@ -28,7 +27,7 @@ - + - - + + - - + + - + @@ -140,10 +139,10 @@ - + - + diff --git a/iOSClient/Share/NCShareUserCell.swift b/iOSClient/Share/NCShareUserCell.swift index 654f2a8dbc..02f6e54a44 100644 --- a/iOSClient/Share/NCShareUserCell.swift +++ b/iOSClient/Share/NCShareUserCell.swift @@ -30,7 +30,7 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { @IBOutlet weak var buttonMenu: UIButton! @IBOutlet weak var imageStatus: UIImageView! @IBOutlet weak var status: UILabel! - @IBOutlet weak var btnQuickStatus: UIButton! + @IBOutlet weak var stackViewQuickStatus: UIStackView! @IBOutlet weak var labelQuickStatus: UILabel! @IBOutlet weak var imageDownArrow: UIImageView! @@ -90,9 +90,9 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { buttonMenu.isHidden = true } - btnQuickStatus.accessibilityHint = NSLocalizedString("_user_sharee_footer_", comment: "") - btnQuickStatus.setTitle("", for: .normal) - btnQuickStatus.contentHorizontalAlignment = .left +// btnQuickStatus.accessibilityHint = NSLocalizedString("_user_sharee_footer_", comment: "") +// btnQuickStatus.setTitle("", for: .normal) +// btnQuickStatus.contentHorizontalAlignment = .left if NCSharePermissions.canEdit(tableShare.permissions, isDirectory: isDirectory) { // Can edit labelQuickStatus.text = NSLocalizedString("_share_editing_", comment: "") @@ -120,6 +120,8 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { NCNetworking.shared.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: tableShare.shareWith, fileName: fileName, account: metadata.account, view: self)) } + stackViewQuickStatus.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(openQuickStatus))) + contentView.bringSubviewToFront(buttonMenu) buttonMenu.menu = nil buttonMenu.showsMenuAsPrimaryAction = true @@ -170,8 +172,8 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { delegate?.tapMenu(with: tableShare, sender: sender) } - @IBAction func quickStatusClicked(_ sender: Any) { - delegate?.quickStatus(with: tableShare, sender: sender) + @objc func openQuickStatus(_ sender: UIGestureRecognizer) { + delegate?.quickStatus(with: tableShare, sender: sender.view ?? sender) } } diff --git a/iOSClient/Share/NCShareUserCell.xib b/iOSClient/Share/NCShareUserCell.xib index afd9bad812..68fd98196a 100755 --- a/iOSClient/Share/NCShareUserCell.xib +++ b/iOSClient/Share/NCShareUserCell.xib @@ -1,8 +1,8 @@ - + - + @@ -18,7 +18,7 @@ - + @@ -35,51 +35,52 @@ - + - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - + - - + + - - - - - + - - @@ -128,13 +122,13 @@ - + From 14c93ab450185e4031a4bd6af72df66015ac16da Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 2 Feb 2026 12:03:40 +0100 Subject: [PATCH 09/19] Refactor Signed-off-by: Milen Pivchev --- iOSClient/Share/NCShareLinkCell.swift | 32 --------------------------- iOSClient/Share/NCShareUserCell.swift | 19 ---------------- iOSClient/Trash/NCTrash.swift | 1 - 3 files changed, 52 deletions(-) diff --git a/iOSClient/Share/NCShareLinkCell.swift b/iOSClient/Share/NCShareLinkCell.swift index 4319a1fbb9..3caf81e644 100644 --- a/iOSClient/Share/NCShareLinkCell.swift +++ b/iOSClient/Share/NCShareLinkCell.swift @@ -37,7 +37,6 @@ class NCShareLinkCell: UITableViewCell { var tableShare: tableShare? var isDirectory = false weak var delegate: NCShareLinkCellDelegate? -// weak var shareController: NCShare? var isInternalLink = false var indexPath = IndexPath() let utility = NCUtility() @@ -118,37 +117,6 @@ class NCShareLinkCell: UITableViewCell { contentView.bringSubviewToFront(menuButton) menuButton.menu = nil menuButton.showsMenuAsPrimaryAction = true - -// contentView.bringSubviewToFront() - -// -// // Configure native context menus -// if let tableShare, let shareController { -// let contextMenu = NCContextMenuShare( -// share: tableShare, -// isDirectory: isDirectory, -// canReshare: shareController.canReshare, -// shareController: shareController -// ) -// menuButton.menu = contextMenu.viewMenu() -// menuButton.showsMenuAsPrimaryAction = true -// -// // Create an invisible button over the status stack for quick permissions menu -// let quickMenuButton = UIButton(type: .system) -// quickMenuButton.translatesAutoresizingMaskIntoConstraints = false -// statusStackView.addSubview(quickMenuButton) -// NSLayoutConstraint.activate([ -// quickMenuButton.leadingAnchor.constraint(equalTo: statusStackView.leadingAnchor), -// quickMenuButton.trailingAnchor.constraint(equalTo: statusStackView.trailingAnchor), -// quickMenuButton.topAnchor.constraint(equalTo: statusStackView.topAnchor), -// quickMenuButton.bottomAnchor.constraint(equalTo: statusStackView.bottomAnchor) -// ]) -// quickMenuButton.menu = contextMenu.quickPermissionsMenu() -// quickMenuButton.showsMenuAsPrimaryAction = true -// } else { -// // For internal link or add new link, keep the old gesture -// statusStackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(openQuickStatus))) -// } } @IBAction func touchUpCopy(_ sender: Any) { diff --git a/iOSClient/Share/NCShareUserCell.swift b/iOSClient/Share/NCShareUserCell.swift index 02f6e54a44..9ac8cb96f3 100644 --- a/iOSClient/Share/NCShareUserCell.swift +++ b/iOSClient/Share/NCShareUserCell.swift @@ -90,10 +90,6 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { buttonMenu.isHidden = true } -// btnQuickStatus.accessibilityHint = NSLocalizedString("_user_sharee_footer_", comment: "") -// btnQuickStatus.setTitle("", for: .normal) -// btnQuickStatus.contentHorizontalAlignment = .left - if NCSharePermissions.canEdit(tableShare.permissions, isDirectory: isDirectory) { // Can edit labelQuickStatus.text = NSLocalizedString("_share_editing_", comment: "") } else if tableShare.permissions == NKShare.Permission.read.rawValue { // Read only @@ -125,21 +121,6 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { contentView.bringSubviewToFront(buttonMenu) buttonMenu.menu = nil buttonMenu.showsMenuAsPrimaryAction = true - -// // Configure native context menus -// if let shareController { -// let contextMenu = NCContextMenuShare( -// share: tableShare, -// isDirectory: isDirectory, -// canReshare: shareController.canReshare, -// shareController: shareController -// ) -// buttonMenu.menu = contextMenu.viewMenu() -// buttonMenu.showsMenuAsPrimaryAction = true -// -// btnQuickStatus.menu = contextMenu.quickPermissionsMenu() -// btnQuickStatus.showsMenuAsPrimaryAction = true -// } } private func getTypeString(_ tableShare: tableShareV2) -> String { diff --git a/iOSClient/Trash/NCTrash.swift b/iOSClient/Trash/NCTrash.swift index 45937ff2b5..9b4c8cea00 100644 --- a/iOSClient/Trash/NCTrash.swift +++ b/iOSClient/Trash/NCTrash.swift @@ -148,7 +148,6 @@ class NCTrash: UIViewController, NCTrashListCellDelegate, NCTrashGridCellDelegat } func tapMoreGridItem(with objectId: String, image: UIImage?, sender: Any) { - // Menu is now shown via native context menu on the button if isEditMode, let button = sender as? UIView { let buttonPosition = button.convert(CGPoint.zero, to: collectionView) let indexPath = collectionView.indexPathForItem(at: buttonPosition) From 80d5305f8b0e0558c587a869012d508afe9c10b3 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 2 Feb 2026 14:31:47 +0100 Subject: [PATCH 10/19] WIP Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 4 + iOSClient/Activity/NCActivity.swift | 10 +- .../Activity/NCActivityTableViewCell.swift | 23 ++- iOSClient/Menu/NCContextMenuProfile.swift | 183 ++++++++++++++++++ iOSClient/Menu/UIViewController+Menu.swift | 79 +------- iOSClient/Share/NCShare+NCCellDelegate.swift | 6 +- iOSClient/Share/NCShare.swift | 24 ++- iOSClient/Share/NCShareCommentsCell.swift | 24 ++- iOSClient/Share/NCShareUserCell.swift | 29 +-- 9 files changed, 271 insertions(+), 111 deletions(-) create mode 100644 iOSClient/Menu/NCContextMenuProfile.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 64c70f3a8e..26464a378b 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -625,6 +625,7 @@ F78B87E72B62527100C65ADC /* NCMediaDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78B87E62B62527100C65ADC /* NCMediaDataSource.swift */; }; F78B87E92B62550800C65ADC /* NCMediaDownloadThumbnail.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnail.swift */; }; F78C6FDE296D677300C952C3 /* NCContextMenuMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78C6FDD296D677300C952C3 /* NCContextMenuMain.swift */; }; + A5A87F9E4B0E4441A6A4BC20 /* NCContextMenuProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7697C94BA14450A0867940 /* NCContextMenuProfile.swift */; }; F78E2D6529AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; }; F78E2D6629AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; }; F78E2D6729AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; }; @@ -1547,6 +1548,7 @@ F78B87E62B62527100C65ADC /* NCMediaDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaDataSource.swift; sourceTree = ""; }; F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaDownloadThumbnail.swift; sourceTree = ""; }; F78C6FDD296D677300C952C3 /* NCContextMenuMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuMain.swift; sourceTree = ""; }; + BB7697C94BA14450A0867940 /* NCContextMenuProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuProfile.swift; sourceTree = ""; }; F78D6F461F0B7CB9002F9619 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Localizable.strings"; sourceTree = ""; }; F78D6F4D1F0B7CE4002F9619 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nb-NO"; path = "nb-NO.lproj/Localizable.strings"; sourceTree = ""; }; F78D6F541F0B7D47002F9619 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; @@ -2007,6 +2009,7 @@ F3DDB2072F2A4A8E008A9B32 /* NCMenu+FloatingPanel.swift */, F376A3732E5CC5FF0067EE25 /* ContextMenuActions.swift */, F78C6FDD296D677300C952C3 /* NCContextMenuMain.swift */, + BB7697C94BA14450A0867940 /* NCContextMenuProfile.swift */, AF68326927BE65A90010BF0B /* NCMenuAction.swift */, F7FAFD3928BFA947000777FE /* NCContextMenuNotification.swift */, AF93471127E2341B002537EE /* NCContextMenuShare.swift */, @@ -4428,6 +4431,7 @@ files = ( F77444F522281649000D5EB0 /* NCMediaCell.swift in Sources */, F78C6FDE296D677300C952C3 /* NCContextMenuMain.swift in Sources */, + A5A87F9E4B0E4441A6A4BC20 /* NCContextMenuProfile.swift in Sources */, F7E402332BA89551007E5609 /* NCTrash+Networking.swift in Sources */, F7E7AEA72BA32D0000512E52 /* NCCollectionViewUnifiedSearch.swift in Sources */, F73EF7A72B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */, diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index 6c1f6b70f3..039bb6bec5 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -217,6 +217,7 @@ extension NCActivity: UITableViewDataSource { cell.indexPath = indexPath cell.tableComments = comment cell.delegate = self + cell.configureAvatarMenu() // Avatar let fileName = NCSession.shared.getFileName(urlBase: metadata.urlBase, user: comment.actorId) @@ -306,6 +307,7 @@ extension NCActivity: UITableViewDataSource { cell.avatar.isHidden = false cell.fileUser = activity.user cell.subjectLeadingConstraint.constant = 15 + cell.configureAvatarMenu() let fileName = NCSession.shared.getFileName(urlBase: session.urlBase, user: activity.user) let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) @@ -532,11 +534,9 @@ extension NCActivity { } extension NCActivity: NCShareCommentsCellDelegate { - func showProfile(with tableComment: tableComments?, sender: Any) { - guard let tableComment = tableComment else { - return - } - self.showProfileMenu(userId: tableComment.actorId, session: session, sender: sender) + func profileMenu(with tableComment: tableComments?) -> UIMenu? { + guard let tableComment = tableComment else { return nil } + return profileMenu(userId: tableComment.actorId, session: session) } func tapMenu(with tableComments: tableComments?, sender: Any) { diff --git a/iOSClient/Activity/NCActivityTableViewCell.swift b/iOSClient/Activity/NCActivityTableViewCell.swift index 1995413a53..e4aee70e03 100644 --- a/iOSClient/Activity/NCActivityTableViewCell.swift +++ b/iOSClient/Activity/NCActivityTableViewCell.swift @@ -22,6 +22,7 @@ class NCActivityTableViewCell: UITableViewCell, NCCellProtocol { private var user: String = "" private var index = IndexPath() + private var avatarButton: UIButton! var idActivity: Int = 0 var activityPreviews: [tableActivityPreview] = [] @@ -47,13 +48,25 @@ class NCActivityTableViewCell: UITableViewCell, NCCellProtocol { override func awakeFromNib() { super.awakeFromNib() - let avatarRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapAvatarImage(_:))) - avatar.addGestureRecognizer(avatarRecognizer) + avatarButton = UIButton(type: .system) + avatarButton.translatesAutoresizingMaskIntoConstraints = false + avatarButton.backgroundColor = .clear + contentView.addSubview(avatarButton) + NSLayoutConstraint.activate([ + avatarButton.topAnchor.constraint(equalTo: avatar.topAnchor), + avatarButton.bottomAnchor.constraint(equalTo: avatar.bottomAnchor), + avatarButton.leadingAnchor.constraint(equalTo: avatar.leadingAnchor), + avatarButton.trailingAnchor.constraint(equalTo: avatar.trailingAnchor) + ]) + avatarButton.showsMenuAsPrimaryAction = true } - @objc func tapAvatarImage(_ sender: Any?) { - guard let fileUser = fileUser else { return } - viewController.showProfileMenu(userId: fileUser, session: NCSession.shared.getSession(account: account), sender: sender) + func configureAvatarMenu() { + guard let fileUser = fileUser else { + avatarButton.menu = nil + return + } + avatarButton.menu = viewController.profileMenu(userId: fileUser, session: NCSession.shared.getSession(account: account)) } } diff --git a/iOSClient/Menu/NCContextMenuProfile.swift b/iOSClient/Menu/NCContextMenuProfile.swift new file mode 100644 index 0000000000..f238e3d564 --- /dev/null +++ b/iOSClient/Menu/NCContextMenuProfile.swift @@ -0,0 +1,183 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Milen Pivchev +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation +import UIKit +import MessageUI +import SVGKit +import NextcloudKit + +/// A context menu for user profile actions (email, talk, etc.) +/// See ``NCShare``, ``NCActivity``, ``NCActivityTableViewCell`` for usage details. +class NCContextMenuProfile: NSObject { + let userId: String + let session: NCSession.Session + let viewController: UIViewController + let utility = NCUtility() + + init(userId: String, session: NCSession.Session, viewController: UIViewController) { + self.userId = userId + self.session = session + self.viewController = viewController + } + + // MARK: - Public Menu Builder + + /// Returns a UIMenu that loads the hovercard data asynchronously using UIDeferredMenuElement + func viewMenu() -> UIMenu { + let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() + + guard capabilities.serverVersionMajor >= NCGlobal.shared.nextcloudVersion23 else { + return UIMenu() + } + + let deferredElement = UIDeferredMenuElement.uncached { completion in + Task { + let menuElements = await self.loadProfileMenu() + await MainActor.run { + completion(menuElements) + } + } + } + + return UIMenu(title: "", children: [deferredElement]) + } + + // MARK: - Private Async Loading + + private func loadProfileMenu() async -> [UIMenuElement] { + let results = await NextcloudKit.shared.getHovercardAsync( + for: userId, + account: session.account + ) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier( + account: self.session.account, + path: self.userId, + name: "getHovercard" + ) + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + + guard let card = results.result, results.account == session.account else { + return [] + } + + return buildProfileMenu(from: card) + } + + // MARK: - Builder Methods + + private func buildProfileMenu(from card: NKHovercard) -> [UIMenuElement] { + var menuElements: [UIMenuElement] = [] + + // Header action (display name with avatar) + let headerAction = makeHeaderAction(card: card) + menuElements.append(headerAction) + + // Action items from hovercard + let actionsMenu = buildActionsMenu(from: card.actions) + if !actionsMenu.isEmpty { + let actionsSection = UIMenu(title: "", options: .displayInline, children: actionsMenu) + menuElements.append(actionsSection) + } + + return menuElements + } + + private func buildActionsMenu(from actions: [NKHovercard.Action]) -> [UIMenuElement] { + return actions.map { makeActionItem(from: $0) } + } + + // MARK: - Action Makers + + private func makeHeaderAction(card: NKHovercard) -> UIAction { + let avatarImage = utility.loadUserImage( + for: userId, + displayName: card.displayName, + urlBase: session.urlBase + ) + + return UIAction( + title: card.displayName, + image: avatarImage, + attributes: .disabled + ) { _ in } + } + + private func makeActionItem(from action: NKHovercard.Action) -> UIAction { + var image = utility.loadImage(named: "person", colors: [NCBrandColor.shared.iconImageColor]) + + if let url = URL(string: action.icon), + let svgSource = SVGKSourceURL.source(from: url), + let svg = SVGKImage(source: svgSource) { + image = svg.uiImage.withTintColor( + NCBrandColor.shared.iconImageColor, + renderingMode: .alwaysOriginal + ) + } + + return UIAction( + title: action.title, + image: image + ) { _ in + self.handleProfileAction(action) + } + } + + // MARK: - Action Handlers + + private func handleProfileAction(_ action: NKHovercard.Action) { + switch action.appId { + case "email": + handleEmailAction(action) + + case "spreed": + handleSpreedAction(action) + + default: + handleDefaultAction(action) + } + } + + private func handleEmailAction(_ action: NKHovercard.Action) { + guard let url = action.hyperlinkUrl, + url.scheme == "mailto", + let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + showError("_cannot_send_mail_error_") + return + } + + viewController.sendEmail(to: components.path) + } + + private func handleSpreedAction(_ action: NKHovercard.Action) { + guard let talkUrl = URL(string: "nextcloudtalk://open-conversation?server=\(session.urlBase)&user=\(session.userId)&withUser=\(userId)"), + UIApplication.shared.canOpenURL(talkUrl) else { + handleDefaultAction(action) + return + } + + UIApplication.shared.open(talkUrl) + } + + private func handleDefaultAction(_ action: NKHovercard.Action) { + guard let url = action.hyperlinkUrl, + UIApplication.shared.canOpenURL(url) else { + showError("_open_url_error_") + return + } + + UIApplication.shared.open(url, options: [:]) + } + + private func showError(_ errorKey: String) { + let error = NKError( + errorCode: NCGlobal.shared.errorInternalError, + errorDescription: errorKey + ) + NCContentPresenter().showError(error: error) + } +} diff --git a/iOSClient/Menu/UIViewController+Menu.swift b/iOSClient/Menu/UIViewController+Menu.swift index f6d824a2cf..ab5348b1c7 100644 --- a/iOSClient/Menu/UIViewController+Menu.swift +++ b/iOSClient/Menu/UIViewController+Menu.swift @@ -24,80 +24,17 @@ import Foundation import UIKit import MessageUI -import SVGKit import NextcloudKit extension UIViewController { - fileprivate func handleProfileAction(_ action: NKHovercard.Action, for userId: String, session: NCSession.Session) { - switch action.appId { - case "email": - guard - let url = action.hyperlinkUrl, - url.scheme == "mailto", - let components = URLComponents(url: url, resolvingAgainstBaseURL: false) - else { - let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_cannot_send_mail_error_") - NCContentPresenter().showError(error: error) - return - } - sendEmail(to: components.path) - - case "spreed": - guard - let talkUrl = URL(string: "nextcloudtalk://open-conversation?server=\(session.urlBase)&user=\(session.userId)&withUser=\(userId)"), - UIApplication.shared.canOpenURL(talkUrl) - else { fallthrough /* default: open web link in browser */ } - UIApplication.shared.open(talkUrl) - - default: - guard let url = action.hyperlinkUrl, UIApplication.shared.canOpenURL(url) else { - let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_open_url_error_") - NCContentPresenter().showError(error: error) - return - } - UIApplication.shared.open(url, options: [:]) - } - } - - func showProfileMenu(userId: String, session: NCSession.Session, sender: Any?) { - let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() - guard capabilities.serverVersionMajor >= NCGlobal.shared.nextcloudVersion23 else { - return - } - - NextcloudKit.shared.getHovercard(for: userId, account: session.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: session.account, - path: userId, - name: "getHovercard") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { account, card, _, _ in - guard let card = card, account == session.account else { return } - - let personHeader = NCMenuAction( - title: card.displayName, - icon: NCUtility().loadUserImage(for: userId, displayName: card.displayName, urlBase: session.urlBase), - sender: sender, - action: nil) - - let actions = card.actions.map { action -> NCMenuAction in - var image = NCUtility().loadImage(named: "user", colors: [NCBrandColor.shared.iconImageColor]) - if let url = URL(string: action.icon), - let svgSource = SVGKSourceURL.source(from: url), - let svg = SVGKImage(source: svgSource) { - image = svg.uiImage.withTintColor(NCBrandColor.shared.iconImageColor, renderingMode: .alwaysOriginal) - } - return NCMenuAction( - title: action.title, - icon: image, - sender: sender, - action: { _ in self.handleProfileAction(action, for: userId, session: session) }) - } - - let allActions = [personHeader] + actions - self.presentMenu(with: allActions, sender: sender) - } + /// Creates a profile context menu for the given user + /// Returns a UIMenu for use with button.menu + func profileMenu(userId: String, session: NCSession.Session) -> UIMenu { + return NCContextMenuProfile( + userId: userId, + session: session, + viewController: self + ).viewMenu() } func sendEmail(to email: String) { diff --git a/iOSClient/Share/NCShare+NCCellDelegate.swift b/iOSClient/Share/NCShare+NCCellDelegate.swift index 69a0437366..37b2767908 100644 --- a/iOSClient/Share/NCShare+NCCellDelegate.swift +++ b/iOSClient/Share/NCShare+NCCellDelegate.swift @@ -54,9 +54,9 @@ extension NCShare: NCShareLinkCellDelegate, NCShareUserCellDelegate { } } - func showProfile(with tableShare: tableShare?, sender: Any) { - guard let tableShare else { return } - showProfileMenu(userId: tableShare.shareWith, session: session, sender: sender) + func profileMenu(with tableShare: tableShare?) -> UIMenu? { + guard let tableShare else { return nil } + return profileMenu(userId: tableShare.shareWith, session: session) } func quickStatus(with tableShare: tableShare?, sender: Any) { diff --git a/iOSClient/Share/NCShare.swift b/iOSClient/Share/NCShare.swift index f9fdc29479..e40353a76b 100644 --- a/iOSClient/Share/NCShare.swift +++ b/iOSClient/Share/NCShare.swift @@ -65,6 +65,7 @@ class NCShare: UIViewController, NCSharePagingContent { var capabilities = NKCapabilities.Capabilities() private var dropDown = DropDown() + private var avatarButton: UIButton! var networking: NCShareNetworking? // MARK: - View Life Cycle @@ -144,10 +145,18 @@ class NCShare: UIViewController, NCSharePagingContent { sharedWithYouByImage.image = utility.loadUserImage(for: metadata.ownerId, displayName: metadata.ownerDisplayName, urlBase: session.urlBase) sharedWithYouByLabel.accessibilityHint = NSLocalizedString("_show_profile_", comment: "") - let shareAction = UITapGestureRecognizer(target: self, action: #selector(openShareProfile(_:))) - sharedWithYouByImage.addGestureRecognizer(shareAction) - let shareLabelAction = UITapGestureRecognizer(target: self, action: #selector(openShareProfile(_:))) - sharedWithYouByLabel.addGestureRecognizer(shareLabelAction) + avatarButton = UIButton(type: .system) + avatarButton.translatesAutoresizingMaskIntoConstraints = false + avatarButton.backgroundColor = .clear + sharedWithYouByView.addSubview(avatarButton) + NSLayoutConstraint.activate([ + avatarButton.topAnchor.constraint(equalTo: sharedWithYouByImage.topAnchor), + avatarButton.bottomAnchor.constraint(equalTo: sharedWithYouByImage.bottomAnchor), + avatarButton.leadingAnchor.constraint(equalTo: sharedWithYouByImage.leadingAnchor), + avatarButton.trailingAnchor.constraint(equalTo: sharedWithYouByLabel.trailingAnchor) + ]) + avatarButton.showsMenuAsPrimaryAction = true + avatarButton.menu = profileMenu(userId: metadata.ownerId, session: session) let fileName = NCSession.shared.getFileName(urlBase: session.urlBase, user: metadata.ownerId) let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) @@ -183,12 +192,6 @@ class NCShare: UIViewController, NCSharePagingContent { reloadData() } - // MARK: - Notification Center - - @objc func openShareProfile(_ sender: UITapGestureRecognizer) { - self.showProfileMenu(userId: metadata.ownerId, session: session, sender: sender.view) - } - // MARK: - @objc func reloadData() { @@ -560,3 +563,4 @@ extension NCShare { return emailPred.evaluate(with: email) } } + diff --git a/iOSClient/Share/NCShareCommentsCell.swift b/iOSClient/Share/NCShareCommentsCell.swift index 9c12503cea..ed92192f64 100644 --- a/iOSClient/Share/NCShareCommentsCell.swift +++ b/iOSClient/Share/NCShareCommentsCell.swift @@ -35,6 +35,7 @@ class NCShareCommentsCell: UITableViewCell, NCCellProtocol { @IBOutlet weak var labelMessage: UILabel! private var index = IndexPath() + private var avatarButton: UIButton! var tableComments: tableComments? weak var delegate: NCShareCommentsCellDelegate? @@ -54,12 +55,25 @@ class NCShareCommentsCell: UITableViewCell, NCCellProtocol { override func awakeFromNib() { super.awakeFromNib() - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapAvatarImage(_:))) - imageItem?.addGestureRecognizer(tapGesture) + avatarButton = UIButton(type: .system) + avatarButton.translatesAutoresizingMaskIntoConstraints = false + avatarButton.backgroundColor = .clear + contentView.addSubview(avatarButton) + NSLayoutConstraint.activate([ + avatarButton.topAnchor.constraint(equalTo: imageItem.topAnchor), + avatarButton.bottomAnchor.constraint(equalTo: imageItem.bottomAnchor), + avatarButton.leadingAnchor.constraint(equalTo: imageItem.leadingAnchor), + avatarButton.trailingAnchor.constraint(equalTo: imageItem.trailingAnchor) + ]) + avatarButton.showsMenuAsPrimaryAction = true } - @objc func tapAvatarImage(_ sender: UITapGestureRecognizer) { - self.delegate?.showProfile(with: tableComments, sender: sender) + func configureAvatarMenu() { + guard let tableComments = tableComments else { + avatarButton.menu = nil + return + } + avatarButton.menu = delegate?.profileMenu(with: tableComments) } @IBAction func touchUpInsideMenu(_ sender: Any) { @@ -69,5 +83,5 @@ class NCShareCommentsCell: UITableViewCell, NCCellProtocol { protocol NCShareCommentsCellDelegate: AnyObject { func tapMenu(with tableComments: tableComments?, sender: Any) - func showProfile(with tableComment: tableComments?, sender: Any) + func profileMenu(with tableComment: tableComments?) -> UIMenu? } diff --git a/iOSClient/Share/NCShareUserCell.swift b/iOSClient/Share/NCShareUserCell.swift index 9ac8cb96f3..47d02cd889 100644 --- a/iOSClient/Share/NCShareUserCell.swift +++ b/iOSClient/Share/NCShareUserCell.swift @@ -35,12 +35,12 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { @IBOutlet weak var imageDownArrow: UIImageView! private var index = IndexPath() + private var avatarButton: UIButton! var tableShare: tableShare? var isDirectory = false let utility = NCUtility() weak var delegate: NCShareUserCellDelegate? -// weak var shareController: NCShare? var indexPath: IndexPath { get { return index } @@ -58,10 +58,6 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { guard let tableShare = tableShare else { return } - self.accessibilityCustomActions = [UIAccessibilityCustomAction( - name: NSLocalizedString("_show_profile_", comment: ""), - target: self, - selector: #selector(tapAvatarImage(_:)))] labelTitle.text = (tableShare.shareWithDisplayname.isEmpty ? tableShare.shareWith : tableShare.shareWithDisplayname) let type = getTypeString(tableShare) @@ -121,6 +117,9 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { contentView.bringSubviewToFront(buttonMenu) buttonMenu.menu = nil buttonMenu.showsMenuAsPrimaryAction = true + + // Configure avatar menu + avatarButton.menu = delegate?.profileMenu(with: tableShare) } private func getTypeString(_ tableShare: tableShareV2) -> String { @@ -138,17 +137,23 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { override func awakeFromNib() { super.awakeFromNib() - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapAvatarImage(_:))) - imageItem?.addGestureRecognizer(tapGesture) + + avatarButton = UIButton(type: .system) + avatarButton.translatesAutoresizingMaskIntoConstraints = false + avatarButton.backgroundColor = .clear + contentView.addSubview(avatarButton) + NSLayoutConstraint.activate([ + avatarButton.topAnchor.constraint(equalTo: imageItem.topAnchor), + avatarButton.bottomAnchor.constraint(equalTo: imageItem.bottomAnchor), + avatarButton.leadingAnchor.constraint(equalTo: imageItem.leadingAnchor), + avatarButton.trailingAnchor.constraint(equalTo: imageItem.trailingAnchor) + ]) + avatarButton.showsMenuAsPrimaryAction = true labelQuickStatus.textColor = NCBrandColor.shared.customer imageDownArrow.image = utility.loadImage(named: "arrowtriangle.down.circle", colors: [NCBrandColor.shared.customer]) } - @objc func tapAvatarImage(_ sender: UITapGestureRecognizer) { - delegate?.showProfile(with: tableShare, sender: sender) - } - @IBAction func touchUpInsideMenu(_ sender: Any) { delegate?.tapMenu(with: tableShare, sender: sender) } @@ -160,7 +165,7 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { protocol NCShareUserCellDelegate: AnyObject { func tapMenu(with tableShare: tableShare?, sender: Any) - func showProfile(with tableComment: tableShare?, sender: Any) + func profileMenu(with tableShare: tableShare?) -> UIMenu? func quickStatus(with tableShare: tableShare?, sender: Any) } From 21ef4e26573f9d820b95415e4221a2114d1059f9 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 2 Feb 2026 16:41:58 +0100 Subject: [PATCH 11/19] WIP Signed-off-by: Milen Pivchev --- ExternalResources/NCApplicationHandle.swift | 5 ----- iOSClient/Activity/NCActivity.swift | 4 ++-- .../Activity/NCActivityTableViewCell.swift | 4 +++- iOSClient/Menu/NCContextMenuProfile.swift | 22 ++----------------- iOSClient/Menu/NCMenuAction.swift | 1 - iOSClient/Menu/UIViewController+Menu.swift | 10 --------- iOSClient/Share/NCShare+NCCellDelegate.swift | 6 ++--- iOSClient/Share/NCShare.swift | 2 +- iOSClient/Share/NCShareCommentsCell.swift | 4 ++-- iOSClient/Share/NCShareLinkCell.swift | 4 ++-- iOSClient/Share/NCShareUserCell.swift | 8 +++---- 11 files changed, 19 insertions(+), 51 deletions(-) diff --git a/ExternalResources/NCApplicationHandle.swift b/ExternalResources/NCApplicationHandle.swift index 39585bcd17..83701fc87b 100644 --- a/ExternalResources/NCApplicationHandle.swift +++ b/ExternalResources/NCApplicationHandle.swift @@ -51,11 +51,6 @@ class NCApplicationHandle: NSObject { func downloadedFile(selector: String, metadata: tableMetadata) { } - // class: NCCollectionViewCommon (+Menu) - // func: toggleMenu(metadata: tableMetadata, imageIcon: UIImage?) - func addCollectionViewCommonMenu(metadata: tableMetadata, image: UIImage?, actions: inout [NCMenuAction]) { - } - // class: NCMore // func: loadItems() func loadItems(functionMenu: inout [NKExternalSite]) { diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index 039bb6bec5..5b00bad5b9 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -534,9 +534,9 @@ extension NCActivity { } extension NCActivity: NCShareCommentsCellDelegate { - func profileMenu(with tableComment: tableComments?) -> UIMenu? { + func openProfileMenu(with tableComment: tableComments?) -> UIMenu? { guard let tableComment = tableComment else { return nil } - return profileMenu(userId: tableComment.actorId, session: session) + return NCContextMenuProfile(userId: tableComment.actorId, session: session, viewController: self).viewMenu() } func tapMenu(with tableComments: tableComments?, sender: Any) { diff --git a/iOSClient/Activity/NCActivityTableViewCell.swift b/iOSClient/Activity/NCActivityTableViewCell.swift index e4aee70e03..627a11e8fa 100644 --- a/iOSClient/Activity/NCActivityTableViewCell.swift +++ b/iOSClient/Activity/NCActivityTableViewCell.swift @@ -66,7 +66,9 @@ class NCActivityTableViewCell: UITableViewCell, NCCellProtocol { avatarButton.menu = nil return } - avatarButton.menu = viewController.profileMenu(userId: fileUser, session: NCSession.shared.getSession(account: account)) + let session = NCSession.shared.getSession(account: account) + + avatarButton.menu = NCContextMenuProfile(userId: fileUser, session: session, viewController: viewController).viewMenu() } } diff --git a/iOSClient/Menu/NCContextMenuProfile.swift b/iOSClient/Menu/NCContextMenuProfile.swift index f238e3d564..42ebe4ce55 100644 --- a/iOSClient/Menu/NCContextMenuProfile.swift +++ b/iOSClient/Menu/NCContextMenuProfile.swift @@ -73,11 +73,6 @@ class NCContextMenuProfile: NSObject { private func buildProfileMenu(from card: NKHovercard) -> [UIMenuElement] { var menuElements: [UIMenuElement] = [] - // Header action (display name with avatar) - let headerAction = makeHeaderAction(card: card) - menuElements.append(headerAction) - - // Action items from hovercard let actionsMenu = buildActionsMenu(from: card.actions) if !actionsMenu.isEmpty { let actionsSection = UIMenu(title: "", options: .displayInline, children: actionsMenu) @@ -93,20 +88,6 @@ class NCContextMenuProfile: NSObject { // MARK: - Action Makers - private func makeHeaderAction(card: NKHovercard) -> UIAction { - let avatarImage = utility.loadUserImage( - for: userId, - displayName: card.displayName, - urlBase: session.urlBase - ) - - return UIAction( - title: card.displayName, - image: avatarImage, - attributes: .disabled - ) { _ in } - } - private func makeActionItem(from action: NKHovercard.Action) -> UIAction { var image = utility.loadImage(named: "person", colors: [NCBrandColor.shared.iconImageColor]) @@ -121,7 +102,8 @@ class NCContextMenuProfile: NSObject { return UIAction( title: action.title, - image: image + image: image, + attributes: action.appId == "timezone" ? .disabled : [] ) { _ in self.handleProfileAction(action) } diff --git a/iOSClient/Menu/NCMenuAction.swift b/iOSClient/Menu/NCMenuAction.swift index 8d8c1d11c9..58905ddded 100644 --- a/iOSClient/Menu/NCMenuAction.swift +++ b/iOSClient/Menu/NCMenuAction.swift @@ -26,7 +26,6 @@ import Foundation import UIKit import NextcloudKit -@available(*, deprecated, message: "Change to using iOS native context menus, as well as using ContextMenuActions and NCViewerContextMenu") class NCMenuAction { let accessibilityIdentifier: String? let title: String diff --git a/iOSClient/Menu/UIViewController+Menu.swift b/iOSClient/Menu/UIViewController+Menu.swift index ab5348b1c7..41d27f06c6 100644 --- a/iOSClient/Menu/UIViewController+Menu.swift +++ b/iOSClient/Menu/UIViewController+Menu.swift @@ -27,16 +27,6 @@ import MessageUI import NextcloudKit extension UIViewController { - /// Creates a profile context menu for the given user - /// Returns a UIMenu for use with button.menu - func profileMenu(userId: String, session: NCSession.Session) -> UIMenu { - return NCContextMenuProfile( - userId: userId, - session: session, - viewController: self - ).viewMenu() - } - func sendEmail(to email: String) { guard MFMailComposeViewController.canSendMail() else { let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_cannot_send_mail_error_") diff --git a/iOSClient/Share/NCShare+NCCellDelegate.swift b/iOSClient/Share/NCShare+NCCellDelegate.swift index 37b2767908..396c02312b 100644 --- a/iOSClient/Share/NCShare+NCCellDelegate.swift +++ b/iOSClient/Share/NCShare+NCCellDelegate.swift @@ -54,12 +54,12 @@ extension NCShare: NCShareLinkCellDelegate, NCShareUserCellDelegate { } } - func profileMenu(with tableShare: tableShare?) -> UIMenu? { + func tapProfileMenu(with tableShare: tableShare?) -> UIMenu? { guard let tableShare else { return nil } - return profileMenu(userId: tableShare.shareWith, session: session) + return NCContextMenuProfile(userId: tableShare.shareWith, session: session, viewController: self).viewMenu() } - func quickStatus(with tableShare: tableShare?, sender: Any) { + func tapQuickStatus(with tableShare: tableShare?, sender: Any) { guard let tableShare else { return } presentQuickStatusActionSheet(for: tableShare, sender: sender) } diff --git a/iOSClient/Share/NCShare.swift b/iOSClient/Share/NCShare.swift index e40353a76b..06e63071b1 100644 --- a/iOSClient/Share/NCShare.swift +++ b/iOSClient/Share/NCShare.swift @@ -156,7 +156,7 @@ class NCShare: UIViewController, NCSharePagingContent { avatarButton.trailingAnchor.constraint(equalTo: sharedWithYouByLabel.trailingAnchor) ]) avatarButton.showsMenuAsPrimaryAction = true - avatarButton.menu = profileMenu(userId: metadata.ownerId, session: session) + avatarButton.menu = NCContextMenuProfile(userId: metadata.ownerId, session: session, viewController: self).viewMenu() let fileName = NCSession.shared.getFileName(urlBase: session.urlBase, user: metadata.ownerId) let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) diff --git a/iOSClient/Share/NCShareCommentsCell.swift b/iOSClient/Share/NCShareCommentsCell.swift index ed92192f64..7877fec1f2 100644 --- a/iOSClient/Share/NCShareCommentsCell.swift +++ b/iOSClient/Share/NCShareCommentsCell.swift @@ -73,7 +73,7 @@ class NCShareCommentsCell: UITableViewCell, NCCellProtocol { avatarButton.menu = nil return } - avatarButton.menu = delegate?.profileMenu(with: tableComments) + avatarButton.menu = delegate?.openProfileMenu(with: tableComments) } @IBAction func touchUpInsideMenu(_ sender: Any) { @@ -83,5 +83,5 @@ class NCShareCommentsCell: UITableViewCell, NCCellProtocol { protocol NCShareCommentsCellDelegate: AnyObject { func tapMenu(with tableComments: tableComments?, sender: Any) - func profileMenu(with tableComment: tableComments?) -> UIMenu? + func openProfileMenu(with tableComment: tableComments?) -> UIMenu? } diff --git a/iOSClient/Share/NCShareLinkCell.swift b/iOSClient/Share/NCShareLinkCell.swift index 3caf81e644..2ec69b2b50 100644 --- a/iOSClient/Share/NCShareLinkCell.swift +++ b/iOSClient/Share/NCShareLinkCell.swift @@ -128,12 +128,12 @@ class NCShareLinkCell: UITableViewCell { } @objc func openQuickStatus(_ sender: UITapGestureRecognizer) { - delegate?.quickStatus(with: tableShare, sender: sender.view ?? sender) + delegate?.tapQuickStatus(with: tableShare, sender: sender.view ?? sender) } } protocol NCShareLinkCellDelegate: AnyObject { func tapCopy(with tableShare: tableShare?, sender: Any) func tapMenu(with tableShare: tableShare?, sender: Any) - func quickStatus(with tableShare: tableShare?, sender: Any) + func tapQuickStatus(with tableShare: tableShare?, sender: Any) } diff --git a/iOSClient/Share/NCShareUserCell.swift b/iOSClient/Share/NCShareUserCell.swift index 47d02cd889..ee953deea5 100644 --- a/iOSClient/Share/NCShareUserCell.swift +++ b/iOSClient/Share/NCShareUserCell.swift @@ -119,7 +119,7 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { buttonMenu.showsMenuAsPrimaryAction = true // Configure avatar menu - avatarButton.menu = delegate?.profileMenu(with: tableShare) + avatarButton.menu = delegate?.tapProfileMenu(with: tableShare) } private func getTypeString(_ tableShare: tableShareV2) -> String { @@ -159,14 +159,14 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { } @objc func openQuickStatus(_ sender: UIGestureRecognizer) { - delegate?.quickStatus(with: tableShare, sender: sender.view ?? sender) + delegate?.tapQuickStatus(with: tableShare, sender: sender.view ?? sender) } } protocol NCShareUserCellDelegate: AnyObject { func tapMenu(with tableShare: tableShare?, sender: Any) - func profileMenu(with tableShare: tableShare?) -> UIMenu? - func quickStatus(with tableShare: tableShare?, sender: Any) + func tapProfileMenu(with tableShare: tableShare?) -> UIMenu? + func tapQuickStatus(with tableShare: tableShare?, sender: Any) } // MARK: - NCSearchUserDropDownCell From 4ec365edb65ec06db4f12bedd42a77268fade1ca Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 3 Feb 2026 10:53:12 +0100 Subject: [PATCH 12/19] WIP Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 8 + iOSClient/Activity/NCActivity.swift | 89 +-------- iOSClient/Menu/NCContextMenuComment.swift | 99 ++++++++++ .../Menu/NCContextMenuPlayerTracks.swift | 102 ++++++++++ iOSClient/Share/NCShare.swift | 32 ++-- iOSClient/Share/NCShareCommentsCell.swift | 8 +- .../NCPlayer/NCPlayerToolBar.swift | 177 ++++-------------- 7 files changed, 273 insertions(+), 242 deletions(-) create mode 100644 iOSClient/Menu/NCContextMenuComment.swift create mode 100644 iOSClient/Menu/NCContextMenuPlayerTracks.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 26464a378b..a213fb3e77 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -626,6 +626,8 @@ F78B87E92B62550800C65ADC /* NCMediaDownloadThumbnail.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnail.swift */; }; F78C6FDE296D677300C952C3 /* NCContextMenuMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78C6FDD296D677300C952C3 /* NCContextMenuMain.swift */; }; A5A87F9E4B0E4441A6A4BC20 /* NCContextMenuProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7697C94BA14450A0867940 /* NCContextMenuProfile.swift */; }; + CB3666201AF7550816B5CD6A /* NCContextMenuComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8932E90EC4278026D86CCCC9 /* NCContextMenuComment.swift */; }; + 2F96A1BAFB10ACFEAC68EF1C /* NCContextMenuPlayerTracks.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C7A5B36D1ED178FB6B76CB /* NCContextMenuPlayerTracks.swift */; }; F78E2D6529AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; }; F78E2D6629AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; }; F78E2D6729AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; }; @@ -1549,6 +1551,8 @@ F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaDownloadThumbnail.swift; sourceTree = ""; }; F78C6FDD296D677300C952C3 /* NCContextMenuMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuMain.swift; sourceTree = ""; }; BB7697C94BA14450A0867940 /* NCContextMenuProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuProfile.swift; sourceTree = ""; }; + 8932E90EC4278026D86CCCC9 /* NCContextMenuComment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuComment.swift; sourceTree = ""; }; + B4C7A5B36D1ED178FB6B76CB /* NCContextMenuPlayerTracks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuPlayerTracks.swift; sourceTree = ""; }; F78D6F461F0B7CB9002F9619 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Localizable.strings"; sourceTree = ""; }; F78D6F4D1F0B7CE4002F9619 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nb-NO"; path = "nb-NO.lproj/Localizable.strings"; sourceTree = ""; }; F78D6F541F0B7D47002F9619 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; @@ -2010,6 +2014,8 @@ F376A3732E5CC5FF0067EE25 /* ContextMenuActions.swift */, F78C6FDD296D677300C952C3 /* NCContextMenuMain.swift */, BB7697C94BA14450A0867940 /* NCContextMenuProfile.swift */, + 8932E90EC4278026D86CCCC9 /* NCContextMenuComment.swift */, + B4C7A5B36D1ED178FB6B76CB /* NCContextMenuPlayerTracks.swift */, AF68326927BE65A90010BF0B /* NCMenuAction.swift */, F7FAFD3928BFA947000777FE /* NCContextMenuNotification.swift */, AF93471127E2341B002537EE /* NCContextMenuShare.swift */, @@ -4432,6 +4438,8 @@ F77444F522281649000D5EB0 /* NCMediaCell.swift in Sources */, F78C6FDE296D677300C952C3 /* NCContextMenuMain.swift in Sources */, A5A87F9E4B0E4441A6A4BC20 /* NCContextMenuProfile.swift in Sources */, + CB3666201AF7550816B5CD6A /* NCContextMenuComment.swift in Sources */, + 2F96A1BAFB10ACFEAC68EF1C /* NCContextMenuPlayerTracks.swift in Sources */, F7E402332BA89551007E5609 /* NCTrash+Networking.swift in Sources */, F7E7AEA72BA32D0000512E52 /* NCCollectionViewUnifiedSearch.swift in Sources */, F73EF7A72B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */, diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index 5b00bad5b9..0f953d96f7 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -247,6 +247,7 @@ extension NCActivity: UITableViewDataSource { // Button Menu if comment.actorId == metadata.userId { cell.buttonMenu.isHidden = false + cell.configureCommentMenu() } else { cell.buttonMenu.isHidden = true } @@ -539,86 +540,12 @@ extension NCActivity: NCShareCommentsCellDelegate { return NCContextMenuProfile(userId: tableComment.actorId, session: session, viewController: self).viewMenu() } - func tapMenu(with tableComments: tableComments?, sender: Any) { - toggleMenu(with: tableComments, sender: sender) - } - - func toggleMenu(with tableComments: tableComments?, sender: Any) { - var actions = [NCMenuAction]() - - actions.append( - NCMenuAction( - title: NSLocalizedString("_edit_comment_", comment: ""), - icon: utility.loadImage(named: "pencil", colors: [NCBrandColor.shared.iconImageColor]), - sender: sender, - action: { _ in - guard let metadata = self.metadata, - let tableComments = tableComments else { - return - } - - let alert = UIAlertController(title: NSLocalizedString("_edit_comment_", comment: ""), message: nil, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel, handler: nil)) - - alert.addTextField(configurationHandler: { textField in - textField.placeholder = NSLocalizedString("_new_comment_", comment: "") - }) - - alert.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in - guard let message = alert.textFields?.first?.text, !message.isEmpty else { return } - - NextcloudKit.shared.updateComments(fileId: metadata.fileId, messageId: tableComments.messageId, message: message, account: metadata.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: metadata.account, - path: metadata.fileId, - name: "updateComments") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { _, _, error in - if error == .success { - self.loadComments() - } else { - Task {@MainActor in - await showErrorBanner(controller: self.tabBarController, text: error.errorDescription, errorCode: error.errorCode) - } - } - } - })) - - self.present(alert, animated: true) - } - ) - ) - - actions.append( - NCMenuAction( - title: NSLocalizedString("_delete_comment_", comment: ""), - destructive: true, - icon: utility.loadImage(named: "trash", colors: [.red]), - sender: sender, - action: { _ in - guard let metadata = self.metadata, let tableComments = tableComments else { return } - - NextcloudKit.shared.deleteComments(fileId: metadata.fileId, messageId: tableComments.messageId, account: metadata.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: metadata.account, - path: metadata.fileId, - name: "deleteComments") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { _, _, error in - if error == .success { - self.loadComments() - } else { - Task { - await showErrorBanner(controller: self.tabBarController, text: error.errorDescription, errorCode: error.errorCode) - } - } - } - } - ) - ) - - presentMenu(with: actions, sender: sender) + func commentMenu(with tableComments: tableComments?) -> UIMenu? { + guard let tableComments, let metadata else { return nil } + return NCContextMenuComment( + tableComments: tableComments, + metadata: metadata, + viewController: self + ).viewMenu() } } diff --git a/iOSClient/Menu/NCContextMenuComment.swift b/iOSClient/Menu/NCContextMenuComment.swift new file mode 100644 index 0000000000..c2353d5a7f --- /dev/null +++ b/iOSClient/Menu/NCContextMenuComment.swift @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Milen Pivchev +// SPDX-License-Identifier: GPL-3.0-or-later + +import UIKit +import NextcloudKit + +class NCContextMenuComment: NSObject { + let tableComments: tableComments + let metadata: tableMetadata + let viewController: UIViewController? + private let utility = NCUtility() + + init(tableComments: tableComments, metadata: tableMetadata, viewController: UIViewController?) { + self.tableComments = tableComments + self.metadata = metadata + self.viewController = viewController + } + + func viewMenu() -> UIMenu { + UIMenu(title: "", children: [makeEditAction(), makeDeleteAction()]) + } + + private func makeEditAction() -> UIAction { + UIAction( + title: NSLocalizedString("_edit_comment_", comment: ""), + image: utility.loadImage(named: "pencil", colors: [NCBrandColor.shared.iconImageColor]) + ) { _ in + let alert = UIAlertController(title: NSLocalizedString("_edit_comment_", comment: ""), message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel, handler: nil)) + + alert.addTextField { textField in + textField.placeholder = NSLocalizedString("_new_comment_", comment: "") + textField.text = self.tableComments.message + } + + alert.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default) { _ in + guard let message = alert.textFields?.first?.text, !message.isEmpty else { return } + + NextcloudKit.shared.updateComments( + fileId: self.metadata.fileId, + messageId: self.tableComments.messageId, + message: message, + account: self.metadata.account + ) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier( + account: self.metadata.account, + path: self.metadata.fileId, + name: "updateComments" + ) + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } completion: { _, _, error in + if error == .success { + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) + } else { + Task { @MainActor in + await showErrorBanner(controller: self.viewController?.tabBarController, text: error.errorDescription, errorCode: error.errorCode) + } + } + } + }) + + self.viewController?.present(alert, animated: true) + } + } + + private func makeDeleteAction() -> UIAction { + UIAction( + title: NSLocalizedString("_delete_comment_", comment: ""), + image: utility.loadImage(named: "trash", colors: [.red]), + attributes: .destructive + ) { _ in + NextcloudKit.shared.deleteComments( + fileId: self.metadata.fileId, + messageId: self.tableComments.messageId, + account: self.metadata.account + ) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier( + account: self.metadata.account, + path: self.metadata.fileId, + name: "deleteComments" + ) + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } completion: { _, _, error in + if error == .success { + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) + } else { + Task { @MainActor in + await showErrorBanner(controller: self.viewController?.tabBarController, text: error.errorDescription, errorCode: error.errorCode) + } + } + } + } + } +} diff --git a/iOSClient/Menu/NCContextMenuPlayerTracks.swift b/iOSClient/Menu/NCContextMenuPlayerTracks.swift new file mode 100644 index 0000000000..cc15471709 --- /dev/null +++ b/iOSClient/Menu/NCContextMenuPlayerTracks.swift @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Milen Pivchev +// SPDX-License-Identifier: GPL-3.0-or-later + +import UIKit +import NextcloudKit +import MobileVLCKit + +class NCContextMenuPlayerTracks: NSObject { + enum TrackType { + case subtitle + case audio + } + + let trackType: TrackType + let tracks: [Any] + let trackIndexes: [Any] + let currentIndex: Int? + let ncplayer: NCPlayer? + let metadata: tableMetadata? + let viewerMediaPage: NCViewerMediaPage? + private let database = NCManageDatabase.shared + + init(trackType: TrackType, + tracks: [Any], + trackIndexes: [Any], + currentIndex: Int?, + ncplayer: NCPlayer?, + metadata: tableMetadata?, + viewerMediaPage: NCViewerMediaPage?) { + self.trackType = trackType + self.tracks = tracks + self.trackIndexes = trackIndexes + self.currentIndex = currentIndex + self.ncplayer = ncplayer + self.metadata = metadata + self.viewerMediaPage = viewerMediaPage + } + + func viewMenu() -> UIMenu { + var children: [UIMenuElement] = [] + + // Track selection items + if !tracks.isEmpty { + for index in 0.. UIAction { + UIAction( + title: title, + state: isSelected ? .on : .off + ) { _ in + guard let metadata = self.metadata else { return } + + switch self.trackType { + case .subtitle: + self.ncplayer?.player.currentVideoSubTitleIndex = index + self.database.addVideo(metadata: metadata, currentVideoSubTitleIndex: Int(index)) + case .audio: + self.ncplayer?.player.currentAudioTrackIndex = index + self.database.addVideo(metadata: metadata, currentAudioTrackIndex: Int(index)) + } + } + } + + private func makeAddTrackAction() -> UIAction { + let title = trackType == .subtitle + ? NSLocalizedString("_add_subtitle_", comment: "") + : NSLocalizedString("_add_audio_", comment: "") + + return UIAction(title: title) { _ in + guard let metadata = self.metadata else { return } + let storyboard = UIStoryboard(name: "NCSelect", bundle: nil) + if let navigationController = storyboard.instantiateInitialViewController() as? UINavigationController, + let viewController = navigationController.topViewController as? NCSelect { + + viewController.delegate = self.viewerMediaPage?.currentViewController.playerToolBar + viewController.typeOfCommandView = .nothing + viewController.includeDirectoryE2EEncryption = false + viewController.enableSelectFile = true + viewController.type = self.trackType == .subtitle ? "subtitle" : "audio" + viewController.serverUrl = metadata.serverUrl + viewController.session = NCSession.shared.getSession(account: metadata.account) + viewController.controller = nil + + self.viewerMediaPage?.present(navigationController, animated: true, completion: nil) + } + } + } +} diff --git a/iOSClient/Share/NCShare.swift b/iOSClient/Share/NCShare.swift index 06e63071b1..6319a7d3cc 100644 --- a/iOSClient/Share/NCShare.swift +++ b/iOSClient/Share/NCShare.swift @@ -514,24 +514,26 @@ extension NCShare: CNContactPickerDelegate { } func showEmailList(arrEmail: [String], sender: Any?) { - var actions = [NCMenuAction]() + let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + for email in arrEmail { - actions.append( - NCMenuAction( - title: email, - icon: utility.loadImage(named: "email", colors: [NCBrandColor.shared.iconImageColor]), - selected: false, - on: false, - sender: sender, - action: { _ in - self.searchField?.text = email - self.networking?.getSharees(searchString: email) - } - ) - ) + alert.addAction(UIAlertAction(title: email, style: .default) { _ in + self.searchField?.text = email + self.networking?.getSharees(searchString: email) + }) } + + alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel)) + + // iPad popover support + if let popover = alert.popoverPresentationController { + popover.sourceView = self.view + popover.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0) + popover.permittedArrowDirections = [] + } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.presentMenu(with: actions, sender: sender) + self.present(alert, animated: true) } } } diff --git a/iOSClient/Share/NCShareCommentsCell.swift b/iOSClient/Share/NCShareCommentsCell.swift index 7877fec1f2..9efc22b358 100644 --- a/iOSClient/Share/NCShareCommentsCell.swift +++ b/iOSClient/Share/NCShareCommentsCell.swift @@ -66,6 +66,8 @@ class NCShareCommentsCell: UITableViewCell, NCCellProtocol { avatarButton.trailingAnchor.constraint(equalTo: imageItem.trailingAnchor) ]) avatarButton.showsMenuAsPrimaryAction = true + + buttonMenu.showsMenuAsPrimaryAction = true } func configureAvatarMenu() { @@ -76,12 +78,12 @@ class NCShareCommentsCell: UITableViewCell, NCCellProtocol { avatarButton.menu = delegate?.openProfileMenu(with: tableComments) } - @IBAction func touchUpInsideMenu(_ sender: Any) { - delegate?.tapMenu(with: tableComments, sender: sender) + func configureCommentMenu() { + buttonMenu.menu = delegate?.commentMenu(with: tableComments) } } protocol NCShareCommentsCellDelegate: AnyObject { - func tapMenu(with tableComments: tableComments?, sender: Any) + func commentMenu(with tableComments: tableComments?) -> UIMenu? func openProfileMenu(with tableComment: tableComments?) -> UIMenu? } diff --git a/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift b/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift index 583b4add22..532aa24e75 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift @@ -62,9 +62,11 @@ class NCPlayerToolBar: UIView { subtitleButton.setImage(utility.loadImage(named: "captions.bubble", colors: [.white]), for: .normal) subtitleButton.isEnabled = false + subtitleButton.showsMenuAsPrimaryAction = true audioButton.setImage(utility.loadImage(named: "speaker.zzz", colors: [.white]), for: .normal) audioButton.isEnabled = false + audioButton.showsMenuAsPrimaryAction = true if UIDevice.current.userInterfaceIdiom == .pad { pointSize = 60 @@ -254,18 +256,44 @@ class NCPlayerToolBar: UIView { @IBAction func tapSubTitle(_ sender: Any) { guard let player = ncplayer?.player else { return } - let spuTracks = player.videoSubTitlesNames - let spuTrackIndexes = player.videoSubTitlesIndexes - toggleMenuSubTitle(spuTracks: spuTracks, spuTrackIndexes: spuTrackIndexes, sender: sender) + var currentIndex: Int? + if let data = database.getVideo(metadata: metadata), let idx = data.currentVideoSubTitleIndex { + currentIndex = idx + } else { + currentIndex = Int(player.currentVideoSubTitleIndex) + } + + subtitleButton.menu = NCContextMenuPlayerTracks( + trackType: .subtitle, + tracks: player.videoSubTitlesNames, + trackIndexes: player.videoSubTitlesIndexes, + currentIndex: currentIndex, + ncplayer: ncplayer, + metadata: metadata, + viewerMediaPage: viewerMediaPage + ).viewMenu() } @IBAction func tapAudio(_ sender: Any) { guard let player = ncplayer?.player else { return } - let audioTracks = player.audioTrackNames - let audioTrackIndexes = player.audioTrackIndexes - toggleMenuAudio(audioTracks: audioTracks, audioTrackIndexes: audioTrackIndexes, sender: sender) + var currentIndex: Int? + if let data = database.getVideo(metadata: metadata), let idx = data.currentAudioTrackIndex { + currentIndex = idx + } else { + currentIndex = Int(player.currentAudioTrackIndex) + } + + audioButton.menu = NCContextMenuPlayerTracks( + trackType: .audio, + tracks: player.audioTrackNames, + trackIndexes: player.audioTrackIndexes, + currentIndex: currentIndex, + ncplayer: ncplayer, + metadata: metadata, + viewerMediaPage: viewerMediaPage + ).viewMenu() } @IBAction func tapPlayerPause(_ sender: Any) { @@ -305,143 +333,6 @@ class NCPlayerToolBar: UIView { } } -extension NCPlayerToolBar { - func toggleMenuSubTitle(spuTracks: [Any], spuTrackIndexes: [Any], sender: Any?) { - var actions = [NCMenuAction]() - var subTitleIndex: Int? - - if let data = self.database.getVideo(metadata: metadata), let idx = data.currentVideoSubTitleIndex { - subTitleIndex = idx - } else if let idx = ncplayer?.player.currentVideoSubTitleIndex { - subTitleIndex = Int(idx) - } - - if !spuTracks.isEmpty { - for index in 0...spuTracks.count - 1 { - - guard let title = spuTracks[index] as? String, let idx = spuTrackIndexes[index] as? Int32, let metadata = self.metadata else { return } - - actions.append( - NCMenuAction( - title: title, - icon: UIImage(), - onTitle: title, - onIcon: UIImage(), - selected: (subTitleIndex ?? -9999) == idx, - on: (subTitleIndex ?? -9999) == idx, - sender: sender, - action: { _ in - self.ncplayer?.player.currentVideoSubTitleIndex = idx - self.database.addVideo(metadata: metadata, currentVideoSubTitleIndex: Int(idx)) - } - ) - ) - } - - actions.append(.seperator(order: 0, sender: sender)) - } - - actions.append( - NCMenuAction( - title: NSLocalizedString("_add_subtitle_", comment: ""), - icon: UIImage(), - onTitle: NSLocalizedString("_add_subtitle_", comment: ""), - onIcon: UIImage(), - selected: false, - on: false, - sender: sender, - action: { _ in - - guard let metadata = self.metadata else { return } - let storyboard = UIStoryboard(name: "NCSelect", bundle: nil) - if let navigationController = storyboard.instantiateInitialViewController() as? UINavigationController, - let viewController = navigationController.topViewController as? NCSelect { - - viewController.delegate = self - viewController.typeOfCommandView = .nothing - viewController.includeDirectoryE2EEncryption = false - viewController.enableSelectFile = true - viewController.type = "subtitle" - viewController.serverUrl = metadata.serverUrl - viewController.session = NCSession.shared.getSession(account: metadata.account) - viewController.controller = nil - - self.viewerMediaPage?.present(navigationController, animated: true, completion: nil) - } - } - ) - ) - - viewerMediaPage?.presentMenu(with: actions, menuColor: UIColor(hexString: "#1C1C1EFF"), textColor: .white, sender: sender) - } - - func toggleMenuAudio(audioTracks: [Any], audioTrackIndexes: [Any], sender: Any?) { - var actions = [NCMenuAction]() - var audioIndex: Int? - - if let data = self.database.getVideo(metadata: metadata), let idx = data.currentAudioTrackIndex { - audioIndex = idx - } else if let idx = ncplayer?.player.currentAudioTrackIndex { - audioIndex = Int(idx) - } - - if !audioTracks.isEmpty { - for index in 0...audioTracks.count - 1 { - guard let title = audioTracks[index] as? String, let idx = audioTrackIndexes[index] as? Int32, let metadata = self.metadata else { return } - actions.append( - NCMenuAction( - title: title, - icon: UIImage(), - onTitle: title, - onIcon: UIImage(), - selected: (audioIndex ?? -9999) == idx, - on: (audioIndex ?? -9999) == idx, - sender: sender, - action: { _ in - self.ncplayer?.player.currentAudioTrackIndex = idx - self.database.addVideo(metadata: metadata, currentAudioTrackIndex: Int(idx)) - } - ) - ) - } - - actions.append(.seperator(order: 0, sender: sender)) - } - - actions.append( - NCMenuAction( - title: NSLocalizedString("_add_audio_", comment: ""), - icon: UIImage(), - onTitle: NSLocalizedString("_add_audio_", comment: ""), - onIcon: UIImage(), - selected: false, - on: false, - sender: sender, - action: { _ in - guard let metadata = self.metadata else { return } - let storyboard = UIStoryboard(name: "NCSelect", bundle: nil) - if let navigationController = storyboard.instantiateInitialViewController() as? UINavigationController, - let viewController = navigationController.topViewController as? NCSelect { - - viewController.delegate = self - viewController.typeOfCommandView = .nothing - viewController.includeDirectoryE2EEncryption = false - viewController.enableSelectFile = true - viewController.type = "audio" - viewController.serverUrl = metadata.serverUrl - viewController.session = NCSession.shared.getSession(account: metadata.account) - viewController.controller = nil - - self.viewerMediaPage?.present(navigationController, animated: true, completion: nil) - } - } - ) - ) - - viewerMediaPage?.presentMenu(with: actions, menuColor: UIColor(hexString: "#1C1C1EFF"), textColor: .white, sender: sender) - } -} - extension NCPlayerToolBar: NCSelectDelegate { func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], overwrite: Bool, copy: Bool, move: Bool, session: NCSession.Session) { if let metadata = metadata, let viewerMediaPage = viewerMediaPage { From 15952b02e9236ba233ba34892188118d0dbb5831 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 3 Feb 2026 11:18:10 +0100 Subject: [PATCH 13/19] WIP Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCContextMenuComment.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOSClient/Menu/NCContextMenuComment.swift b/iOSClient/Menu/NCContextMenuComment.swift index c2353d5a7f..5cdce5bb49 100644 --- a/iOSClient/Menu/NCContextMenuComment.swift +++ b/iOSClient/Menu/NCContextMenuComment.swift @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2025 Milen Pivchev +// SPDX-FileCopyrightText: 2026 Milen Pivchev // SPDX-License-Identifier: GPL-3.0-or-later import UIKit @@ -87,7 +87,7 @@ class NCContextMenuComment: NSObject { } } completion: { _, _, error in if error == .success { - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) + (self.viewController as? NCActivity)?.loadComments() } else { Task { @MainActor in await showErrorBanner(controller: self.viewController?.tabBarController, text: error.errorDescription, errorCode: error.errorCode) From 3d3d5c0530fa6fa084c965c555fe20d1392a57c9 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 3 Feb 2026 12:54:57 +0100 Subject: [PATCH 14/19] WIP Signed-off-by: Milen Pivchev --- .../Menu/NCContextMenuPlayerTracks.swift | 68 +++++++++++++++---- .../NCPlayer/NCPlayerToolBar.swift | 7 +- .../NCPlayer/NCPlayerToolBar.xib | 19 ++---- 3 files changed, 67 insertions(+), 27 deletions(-) diff --git a/iOSClient/Menu/NCContextMenuPlayerTracks.swift b/iOSClient/Menu/NCContextMenuPlayerTracks.swift index cc15471709..4cd7a563da 100644 --- a/iOSClient/Menu/NCContextMenuPlayerTracks.swift +++ b/iOSClient/Menu/NCContextMenuPlayerTracks.swift @@ -13,8 +13,6 @@ class NCContextMenuPlayerTracks: NSObject { } let trackType: TrackType - let tracks: [Any] - let trackIndexes: [Any] let currentIndex: Int? let ncplayer: NCPlayer? let metadata: tableMetadata? @@ -29,8 +27,6 @@ class NCContextMenuPlayerTracks: NSObject { metadata: tableMetadata?, viewerMediaPage: NCViewerMediaPage?) { self.trackType = trackType - self.tracks = tracks - self.trackIndexes = trackIndexes self.currentIndex = currentIndex self.ncplayer = ncplayer self.metadata = metadata @@ -40,18 +36,66 @@ class NCContextMenuPlayerTracks: NSObject { func viewMenu() -> UIMenu { var children: [UIMenuElement] = [] - // Track selection items - if !tracks.isEmpty { - for index in 0.. - + - - + @@ -21,17 +20,11 @@ - - -