diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 145c0551cc..2425cfd960 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -1366,6 +1366,7 @@ F3CA337C2D0B2B6A00672333 /* AlbumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumModel.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 = ""; }; + F3F9C4552DC8CAB8001D3E11 /* NextcloudKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = NextcloudKit; path = ../NextcloudKit; sourceTree = SOURCE_ROOT; }; F700222B1EC479840080073F /* Custom.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Custom.xcassets; sourceTree = ""; }; F700510022DF63AC003A3356 /* NCShare.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCShare.storyboard; sourceTree = ""; }; F700510422DF6A89003A3356 /* NCShare.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShare.swift; sourceTree = ""; }; @@ -3271,6 +3272,7 @@ F7F67B9F1A24D27800EE80DA = { isa = PBXGroup; children = ( + F3F9C4552DC8CAB8001D3E11 /* NextcloudKit */, AA8E041E2D3114E200E7E89C /* README.md */, F7B8B82F25681C3400967775 /* GoogleService-Info.plist */, F7C1CDD91E6DFC6F005D92BE /* Brand */, @@ -6209,8 +6211,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/scenee/FloatingPanel"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.0; + kind = exactVersion; + version = 2.8.6; }; }; F77333862927A72100466E35 /* XCRemoteSwiftPackageReference "OpenSSL" */ = { diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index 7e94cae296..1f98631b09 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -32,8 +32,7 @@ class tableMetadata: Object { self.altitude == object.altitude, self.status == object.status, Array(self.tags).elementsEqual(Array(object.tags)), - Array(self.shareType).elementsEqual(Array(object.shareType)), - Array(self.sharePermissionsCloudMesh).elementsEqual(Array(object.sharePermissionsCloudMesh)) { + Array(self.shareType).elementsEqual(Array(object.shareType)) { return true } else { return false @@ -99,8 +98,8 @@ class tableMetadata: Object { @objc dynamic var sessionError = "" @objc dynamic var sessionSelector = "" @objc dynamic var sessionTaskIdentifier: Int = 0 + /// The integer for sharing permissions. @objc dynamic var sharePermissionsCollaborationServices: Int = 0 - let sharePermissionsCloudMesh = List() let shareType = List() @objc dynamic var size: Int64 = 0 @objc dynamic var status: Int = 0 @@ -380,9 +379,7 @@ extension NCManageDatabase { metadata.serverUrl = file.serverUrl metadata.serveUrlFileName = file.serverUrl + "/" + file.fileName metadata.sharePermissionsCollaborationServices = file.sharePermissionsCollaborationServices - for element in file.sharePermissionsCloudMesh { - metadata.sharePermissionsCloudMesh.append(element) - } + for element in file.shareType { metadata.shareType.append(element) } diff --git a/iOSClient/Data/NCManageDatabase+Share.swift b/iOSClient/Data/NCManageDatabase+Share.swift index bb51089730..9b945fc042 100644 --- a/iOSClient/Data/NCManageDatabase+Share.swift +++ b/iOSClient/Data/NCManageDatabase+Share.swift @@ -55,6 +55,8 @@ class tableShareV2: Object { @objc dynamic var sendPasswordByTalk: Bool = false @objc dynamic var serverUrl = "" + /// + /// shareType - (int) 0 = user; 1 = group; 3 = public link; 4 = email; 6 = federated cloud share; 7 = circle; 10 = Talk conversation /// /// See [OCS Share API documentation](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html) for semantic definitions of the different possible values. /// @@ -252,9 +254,9 @@ extension NCManageDatabase { // There is currently only one share attribute “download” from the scope “permissions”. This attribute is only valid for user and group shares, not for public link shares. func setAttibuteDownload(state: Bool) -> String? { if state { - return nil + return "[{\"scope\":\"permissions\",\"key\":\"download\",\"value\":true}]" } else { - return "[{\"scope\":\"permissions\",\"key\":\"download\",\"enabled\":false}]" + return "[{\"scope\":\"permissions\",\"key\":\"download\",\"value\":null}]" } } @@ -264,10 +266,10 @@ extension NCManageDatabase { if let json = try JSONSerialization.jsonObject(with: data) as? [Dictionary] { for sub in json { let key = sub["key"] as? String - let enabled = sub["enabled"] as? Bool + let enabled = (sub["value"] as? Bool) /* >= NC 30 */ ?? sub["enabled"] as? Bool // /* < NC 29 */ let scope = sub["scope"] as? String - if key == "download", scope == "permissions", let enabled = enabled { - return enabled + if key == "download", scope == "permissions" { + return enabled ?? false } } } diff --git a/iOSClient/Menu/NCShare+Menu.swift b/iOSClient/Menu/NCShare+Menu.swift index be44315305..b50d7f7a21 100644 --- a/iOSClient/Menu/NCShare+Menu.swift +++ b/iOSClient/Menu/NCShare+Menu.swift @@ -30,7 +30,7 @@ extension NCShare { let capabilities = NCCapabilities.shared.getCapabilities(account: self.metadata.account) var actions = [NCMenuAction]() - if share.shareType == 3, canReshare { + if share.shareType == NCShareCommon().SHARE_TYPE_LINK, canReshare { actions.append( NCMenuAction( title: NSLocalizedString("_share_add_sharelink_", comment: ""), @@ -95,40 +95,69 @@ extension NCShare { self.presentMenu(with: actions, sender: sender) } - func toggleUserPermissionMenu(isDirectory: Bool, tableShare: tableShare, sender: Any?) { + func toggleQuickPermissionsMenu(isDirectory: Bool, share: tableShare, sender: Any?) { var actions = [NCMenuAction]() let permissions = NCPermissions() - actions.append( - NCMenuAction( + actions.append(contentsOf: + [NCMenuAction( title: NSLocalizedString("_share_read_only_", comment: ""), icon: utility.loadImage(named: "eye", colors: [NCBrandColor.shared.iconImageColor]), - selected: tableShare.permissions == (permissions.permissionReadShare + permissions.permissionShareShare) || tableShare.permissions == permissions.permissionReadShare, + selected: share.permissions == (permissions.permissionReadShare + permissions.permissionShareShare) || share.permissions == permissions.permissionReadShare, on: false, sender: sender, action: { _ in - let canShare = permissions.isPermissionToCanShare(tableShare.permissions) - let permissions = permissions.getPermission(canEdit: false, canCreate: false, canChange: false, canDelete: false, canShare: canShare, isDirectory: isDirectory) - self.updateSharePermissions(share: tableShare, permissions: permissions) + let permissions = permissions.getPermissionValue(canCreate: false, canEdit: false, canDelete: false, canShare: false, isDirectory: isDirectory) + self.updateSharePermissions(share: share, permissions: permissions) } - ) - ) - - actions.append( + ), NCMenuAction( - title: isDirectory ? NSLocalizedString("_share_allow_upload_", comment: "") : NSLocalizedString("_share_editing_", comment: ""), + title: NSLocalizedString("_share_editing_", comment: ""), icon: utility.loadImage(named: "pencil", colors: [NCBrandColor.shared.iconImageColor]), - selected: hasUploadPermission(tableShare: tableShare), + selected: hasUploadPermission(tableShare: share), on: false, sender: sender, action: { _ in - let canShare = permissions.isPermissionToCanShare(tableShare.permissions) - let permissions = permissions.getPermission(canEdit: true, canCreate: true, canChange: true, canDelete: true, canShare: canShare, isDirectory: isDirectory) - self.updateSharePermissions(share: tableShare, permissions: permissions) + let permissions = permissions.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 == NCShareCommon().SHARE_TYPE_LINK /* public link */ || share.shareType == NCShareCommon().SHARE_TYPE_EMAIL) { + actions.insert(NCMenuAction( + title: NSLocalizedString("_share_file_drop_", comment: ""), + icon: utility.loadImage(named: "arrow.up.document", colors: [NCBrandColor.shared.iconImageColor]), + selected: share.permissions == permissions.permissionCreateShare, + on: false, + sender: sender, + action: { _ in + let permissions = permissions.getPermissionValue(canRead: false, canCreate: true, canEdit: false, canDelete: false, canShare: false, isDirectory: isDirectory) + self.updateSharePermissions(share: share, permissions: permissions) + } + ), at: 2) + } + self.presentMenu(with: actions, sender: sender) } @@ -152,6 +181,9 @@ 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 { NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Failed to get download limit from database!") return diff --git a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift index a57e926429..d099a08d1b 100644 --- a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift +++ b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift @@ -27,59 +27,6 @@ import SVGKit import CloudKit class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDelegate, NCShareNavigationTitleSetting { - func dismissShareAdvanceView(shouldSave: Bool) { - guard shouldSave else { - guard oldTableShare?.hasChanges(comparedTo: share) != false else { - navigationController?.popViewController(animated: true) - return - } - - let alert = UIAlertController( - title: NSLocalizedString("_cancel_request_", comment: ""), - message: NSLocalizedString("_discard_changes_info_", comment: ""), - preferredStyle: .alert) - - alert.addAction(UIAlertAction( - title: NSLocalizedString("_discard_changes_", comment: ""), - style: .destructive, - handler: { _ in self.navigationController?.popViewController(animated: true) })) - - alert.addAction(UIAlertAction(title: NSLocalizedString("_continue_editing_", comment: ""), style: .default)) - self.present(alert, animated: true) - - return - } - - Task { - // TODO: Apply share token to download limit object - - if isNewShare { - let serverUrl = metadata.serverUrl + "/" + metadata.fileName - - if share.shareType != NCShareCommon().SHARE_TYPE_LINK, metadata.e2eEncrypted, - NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 { - - if NCNetworkingE2EE().isInUpload(account: metadata.account, serverUrl: serverUrl) { - let error = NKError(errorCode: NCGlobal.shared.errorE2EEUploadInProgress, errorDescription: NSLocalizedString("_e2e_in_upload_", comment: "")) - return NCContentPresenter().showInfo(error: error) - } - - let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, addUserId: share.shareWith, removeUserId: nil, account: metadata.account) - - if error != .success { - return NCContentPresenter().showError(error: error) - } - } - - networking?.createShare(share, downloadLimit: self.downloadLimit) - } else { - networking?.updateShare(share, downloadLimit: self.downloadLimit) - } - } - - navigationController?.popViewController(animated: true) - } - let database = NCManageDatabase.shared var oldTableShare: tableShare? @@ -172,7 +119,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { if section == 0 { - return NSLocalizedString("_permissions_", comment: "") + return NSLocalizedString("_custom_permissions_", comment: "") } else if section == 1 { return NSLocalizedString("_advanced_", comment: "") } else { return nil } @@ -186,7 +133,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg if section == 0 { // check reshare permission, if restricted add note let maxPermission = metadata.directory ? NCPermissions().permissionMaxFolderShare : NCPermissions().permissionMaxFileShare - return shareConfig.resharePermission != maxPermission ? shareConfig.permissions.count + 1 : shareConfig.permissions.count + return shareConfig.sharePermission != maxPermission ? shareConfig.permissions.count + 1 : shareConfig.permissions.count } else if section == 1 { return shareConfig.advanced.count } else { return 0 } @@ -208,7 +155,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) guard let cellConfig = shareConfig.config(for: indexPath) else { return } - guard let cellConfig = cellConfig as? NCShareDetails else { + guard let cellConfig = cellConfig as? NCAdvancedPermission else { cellConfig.didSelect(for: share) tableView.reloadData() return @@ -259,8 +206,66 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg tableView.reloadData() } self.present(alertController, animated: true) + case .downloadAndSync: + share.downloadAndSync.toggle() + tableView.reloadData() } } + + func dismissShareAdvanceView(shouldSave: Bool) { + guard shouldSave else { + guard oldTableShare?.hasChanges(comparedTo: share) != false else { + navigationController?.popViewController(animated: true) + return + } + + let alert = UIAlertController( + title: NSLocalizedString("_cancel_request_", comment: ""), + message: NSLocalizedString("_discard_changes_info_", comment: ""), + preferredStyle: .alert) + + alert.addAction(UIAlertAction( + title: NSLocalizedString("_discard_changes_", comment: ""), + style: .destructive, + handler: { _ in self.navigationController?.popViewController(animated: true) })) + + alert.addAction(UIAlertAction(title: NSLocalizedString("_continue_editing_", comment: ""), style: .default)) + self.present(alert, animated: true) + + return + } + + Task { + if (share.shareType == NCShareCommon().SHARE_TYPE_LINK || share.shareType == NCShareCommon().SHARE_TYPE_EMAIL) && NCPermissions().isPermissionToCanShare(share.permissions) { + share.permissions = share.permissions - NCPermissions().permissionShareShare + } + + if isNewShare { + let serverUrl = metadata.serverUrl + "/" + metadata.fileName + + if share.shareType != NCShareCommon().SHARE_TYPE_LINK, metadata.e2eEncrypted, + NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 { + + if NCNetworkingE2EE().isInUpload(account: metadata.account, serverUrl: serverUrl) { + let error = NKError(errorCode: NCGlobal.shared.errorE2EEUploadInProgress, errorDescription: NSLocalizedString("_e2e_in_upload_", comment: "")) + return NCContentPresenter().showInfo(error: error) + } + + let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, addUserId: share.shareWith, removeUserId: nil, account: metadata.account) + + if error != .success { + return NCContentPresenter().showError(error: error) + } + } + + networking?.createShare(share, downloadLimit: self.downloadLimit) + } else { + networking?.updateShare(share, downloadLimit: self.downloadLimit) + } + } + + navigationController?.popViewController(animated: true) + } } // MARK: - NCShareDownloadLimitTableViewControllerDelegate diff --git a/iOSClient/Share/Advanced/NCShareCells.swift b/iOSClient/Share/Advanced/NCShareCells.swift index b898b1e22b..6042717e37 100644 --- a/iOSClient/Share/Advanced/NCShareCells.swift +++ b/iOSClient/Share/Advanced/NCShareCells.swift @@ -22,6 +22,7 @@ // import UIKit +import OSLog protocol NCShareCellConfig { var title: String { get } @@ -48,44 +49,35 @@ protocol NCPermission: NCToggleCellConfig { static var forDirectory: [Self] { get } static var forFile: [Self] { get } static func forDirectoryE2EE(account: String) -> [NCPermission] - func hasResharePermission(for parentPermission: Int) -> Bool - func hasDownload() -> Bool + func hasPermission(for parentPermission: Int) -> Bool + func hasReadPermission() -> Bool } enum NCUserPermission: CaseIterable, NCPermission { - func hasResharePermission(for parentPermission: Int) -> Bool { - if self == .download { return true } + func hasPermission(for parentPermission: Int) -> Bool { return ((permissionBitFlag & parentPermission) != 0) } - func hasDownload() -> Bool { - return self == .download + func hasReadPermission() -> Bool { + return self == .read } var permissionBitFlag: Int { switch self { + case .read: return NCPermissions().permissionReadShare case .reshare: return NCPermissions().permissionShareShare - case .edit: return NCPermissions().permissionUpdateShare + case .edit: return NCPermissions().permissionEditShare case .create: return NCPermissions().permissionCreateShare case .delete: return NCPermissions().permissionDeleteShare - case .download: return NCPermissions().permissionDownloadShare } } func didChange(_ share: Shareable, to newValue: Bool) { - if self == .download { - share.attributes = NCManageDatabase.shared.setAttibuteDownload(state: newValue) - } else { - share.permissions ^= permissionBitFlag - } + share.permissions ^= permissionBitFlag } func isOn(for share: Shareable) -> Bool { - if self == .download { - return NCManageDatabase.shared.isAttributeDownloadEnabled(attributes: share.attributes) - } else { - return (share.permissions & permissionBitFlag) != 0 - } + return (share.permissions & permissionBitFlag) != 0 } static func forDirectoryE2EE(account: String) -> [NCPermission] { @@ -95,107 +87,72 @@ enum NCUserPermission: CaseIterable, NCPermission { return [] } - case reshare, edit, create, delete, download + case read, reshare, edit, create, delete static let forDirectory: [NCUserPermission] = NCUserPermission.allCases - static let forFile: [NCUserPermission] = [.reshare, .edit] + static let forFile: [NCUserPermission] = [.read, .reshare, .edit] var title: String { switch self { + case .read: return NSLocalizedString("_share_can_read_", comment: "") case .reshare: return NSLocalizedString("_share_can_reshare_", comment: "") case .edit: return NSLocalizedString("_share_can_change_", comment: "") case .create: return NSLocalizedString("_share_can_create_", comment: "") case .delete: return NSLocalizedString("_share_can_delete_", comment: "") - case .download: return NSLocalizedString("_share_can_download_", comment: "") } } } -enum NCLinkPermission: NCPermission { - func didChange(_ share: Shareable, to newValue: Bool) { - guard self != .allowEdit || newValue else { - share.permissions = NCPermissions().permissionReadShare - return +enum NCLinkEmailPermission: CaseIterable, NCPermission { + static func forDirectoryE2EE(account: String) -> [any NCPermission] { + if NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 { + return NCUserPermission.allCases } - share.permissions = permissionValue + return [] } - func hasResharePermission(for parentPermission: Int) -> Bool { - permissionValue & parentPermission == permissionValue + func hasReadPermission() -> Bool { + return self == .read } - func hasDownload() -> Bool { - return false + func hasPermission(for parentPermission: Int) -> Bool { + return ((permissionBitFlag & parentPermission) != 0) } - var permissionValue: Int { + var permissionBitFlag: Int { switch self { - case .allowEdit: - return NCPermissions().getPermission( - canEdit: true, - canCreate: true, - canChange: true, - canDelete: true, - canShare: false, - isDirectory: false) - case .viewOnly: - return NCPermissions().getPermission( - canEdit: false, - canCreate: false, - canChange: false, - canDelete: false, - // not possible to create "read-only" shares without reshare option - // https://github.com/nextcloud/server/blame/f99876997a9119518fe5f7ad3a3a51d33459d4cc/apps/files_sharing/lib/Controller/ShareAPIController.php#L1104-L1107 - canShare: true, - isDirectory: true) - case .uploadEdit: - return NCPermissions().getPermission( - canEdit: true, - canCreate: true, - canChange: true, - canDelete: true, - canShare: false, - isDirectory: true) - case .fileDrop: - return NCPermissions().permissionCreateShare - case .secureFileDrop: - return NCPermissions().permissionCreateShare + case .read: return NCPermissions().permissionReadShare + case .edit: return NCPermissions().permissionEditShare + case .create: return NCPermissions().permissionCreateShare + case .delete: return NCPermissions().permissionDeleteShare } } - func isOn(for share: Shareable) -> Bool { - let permissions = NCPermissions() - switch self { - case .allowEdit: return permissions.isAnyPermissionToEdit(share.permissions) - case .viewOnly: return !permissions.isAnyPermissionToEdit(share.permissions) && share.permissions != permissions.permissionCreateShare - case .uploadEdit: return permissions.isAnyPermissionToEdit(share.permissions) && share.permissions != permissions.permissionCreateShare - case .fileDrop: return share.permissions == permissions.permissionCreateShare - case .secureFileDrop: return share.permissions == permissions.permissionCreateShare - } + func didChange(_ share: Shareable, to newValue: Bool) { + share.permissions ^= permissionBitFlag } - static func forDirectoryE2EE(account: String) -> [NCPermission] { - return [NCLinkPermission.secureFileDrop] + func isOn(for share: Shareable) -> Bool { + return (share.permissions & permissionBitFlag) != 0 } var title: String { switch self { - case .allowEdit: return NSLocalizedString("_share_can_change_", comment: "") - case .viewOnly: return NSLocalizedString("_share_read_only_", comment: "") - case .uploadEdit: return NSLocalizedString("_share_allow_upload_", comment: "") - case .fileDrop: return NSLocalizedString("_share_file_drop_", comment: "") - case .secureFileDrop: return NSLocalizedString("_share_secure_file_drop_", comment: "") + case .read: return NSLocalizedString("_share_can_read_", comment: "") + case .edit: return NSLocalizedString("_share_can_change_", comment: "") + case .create: return NSLocalizedString("_share_can_create_", comment: "") + case .delete: return NSLocalizedString("_share_can_delete_", comment: "") } } - case allowEdit, viewOnly, uploadEdit, fileDrop, secureFileDrop - static let forDirectory: [NCLinkPermission] = [.viewOnly, .uploadEdit, .fileDrop] - static let forFile: [NCLinkPermission] = [.allowEdit] + case edit, read, create, delete + static let forDirectory: [NCLinkEmailPermission] = NCLinkEmailPermission.allCases + static let forFile: [NCLinkEmailPermission] = [.read, .edit] } /// /// Individual aspects of share. /// -enum NCShareDetails: CaseIterable, NCShareCellConfig { +enum NCAdvancedPermission: CaseIterable, NCShareCellConfig { func didSelect(for share: Shareable) { switch self { case .hideDownload: share.hideDownload.toggle() @@ -204,6 +161,7 @@ enum NCShareDetails: CaseIterable, NCShareCellConfig { case .password: return case .note: return case .label: return + case .downloadAndSync: return } } @@ -228,6 +186,8 @@ enum NCShareDetails: CaseIterable, NCShareCellConfig { let cell = UITableViewCell(style: .value1, reuseIdentifier: "shareLabel") cell.detailTextLabel?.text = share.label return cell + case .downloadAndSync: + return NCShareToggleCell(isOn: share.downloadAndSync) } } @@ -239,24 +199,28 @@ enum NCShareDetails: CaseIterable, NCShareCellConfig { case .password: return NSLocalizedString("_share_password_protect_", comment: "") case .note: return NSLocalizedString("_share_note_recipient_", comment: "") case .label: return NSLocalizedString("_share_link_name_", comment: "") + case .downloadAndSync: return NSLocalizedString("_share_can_download_", comment: "") } } - case label, hideDownload, limitDownload, expirationDate, password, note - static let forLink: [NCShareDetails] = NCShareDetails.allCases - static let forUser: [NCShareDetails] = [.expirationDate, .note] + case label, hideDownload, limitDownload, expirationDate, password, note, downloadAndSync + static let forLink: [NCAdvancedPermission] = [.expirationDate, .hideDownload, .label, .limitDownload, .note, .password] + static let forUser: [NCAdvancedPermission] = [.expirationDate, .note, .downloadAndSync] } struct NCShareConfig { let permissions: [NCPermission] - let advanced: [NCShareDetails] - let share: Shareable - let resharePermission: Int + let advanced: [NCAdvancedPermission] + let shareable: Shareable + let sharePermission: Int + let isDirectory: Bool + /// There are many share types, but we only classify them as a link share (link type, email type) and a user share (every other share type). init(parentMetadata: tableMetadata, share: Shareable) { - self.share = share - self.resharePermission = parentMetadata.sharePermissionsCollaborationServices - let type: NCPermission.Type = share.shareType == NCShareCommon().SHARE_TYPE_LINK ? NCLinkPermission.self : NCUserPermission.self + self.shareable = share + self.sharePermission = parentMetadata.sharePermissionsCollaborationServices + self.isDirectory = parentMetadata.directory + let type: NCPermission.Type = (share.shareType == NCShareCommon().SHARE_TYPE_LINK || share.shareType == NCShareCommon().SHARE_TYPE_EMAIL) ? NCLinkEmailPermission.self : NCUserPermission.self self.permissions = parentMetadata.directory ? (parentMetadata.e2eEncrypted ? type.forDirectoryE2EE(account: parentMetadata.account) : type.forDirectory) : type.forFile if share.shareType == NCShareCommon().SHARE_TYPE_LINK { @@ -266,29 +230,44 @@ struct NCShareConfig { .capabilityFileSharingDownloadLimit if parentMetadata.isDirectory || hasDownloadLimitCapability == false { - self.advanced = NCShareDetails.forLink.filter { $0 != .limitDownload } + self.advanced = NCAdvancedPermission.forLink.filter { $0 != .limitDownload } } else { - self.advanced = NCShareDetails.forLink + self.advanced = NCAdvancedPermission.forLink } } else { - self.advanced = NCShareDetails.forUser + self.advanced = NCAdvancedPermission.forUser } } func cellFor(indexPath: IndexPath) -> UITableViewCell? { let cellConfig = config(for: indexPath) - let cell = cellConfig?.getCell(for: share) + let cell = cellConfig?.getCell(for: shareable) cell?.textLabel?.text = cellConfig?.title - if let cellConfig = cellConfig as? NCPermission, !cellConfig.hasResharePermission(for: resharePermission), !cellConfig.hasDownload() { + Logger().info("\(cellConfig?.title ?? "")") + + if let cellConfig = cellConfig as? NCPermission, !cellConfig.hasPermission(for: sharePermission) { cell?.isUserInteractionEnabled = false cell?.textLabel?.isEnabled = false } + + // For user permissions: Read permission is always enabled and we show it as a non-interactable permission for brevity. + if let cellConfig = cellConfig as? NCUserPermission, cellConfig.hasReadPermission() { + cell?.isUserInteractionEnabled = false + cell?.textLabel?.isEnabled = false + } + + // For link permissions: Read permission is always enabled and we show it as a non-interactable permission in files only for brevity. + if let cellConfig = cellConfig as? NCLinkEmailPermission, cellConfig.hasReadPermission(), !isDirectory { + cell?.isUserInteractionEnabled = false + cell?.textLabel?.isEnabled = false + } + return cell } func didSelectRow(at indexPath: IndexPath) { let cellConfig = config(for: indexPath) - cellConfig?.didSelect(for: share) + cellConfig?.didSelect(for: shareable) } func config(for indexPath: IndexPath) -> NCShareCellConfig? { diff --git a/iOSClient/Share/Advanced/NCShareDateCell.swift b/iOSClient/Share/Advanced/NCShareDateCell.swift index 5dfcab9dfc..15ddfa415b 100644 --- a/iOSClient/Share/Advanced/NCShareDateCell.swift +++ b/iOSClient/Share/Advanced/NCShareDateCell.swift @@ -68,8 +68,8 @@ class NCShareDateCell: UITableViewCell { shareCommon.SHARE_TYPE_CIRCLE, shareCommon.SHARE_TYPE_ROOM: return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingInternalExpireDateEnforced - case shareCommon.SHARE_TYPE_REMOTE, - shareCommon.SHARE_TYPE_REMOTE_GROUP: + case shareCommon.SHARE_TYPE_FEDERATED, + shareCommon.SHARE_TYPE_FEDERATED_GROUP: return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingRemoteExpireDateEnforced default: return false @@ -87,8 +87,8 @@ class NCShareDateCell: UITableViewCell { shareCommon.SHARE_TYPE_CIRCLE, shareCommon.SHARE_TYPE_ROOM: return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingInternalExpireDateDays - case shareCommon.SHARE_TYPE_REMOTE, - shareCommon.SHARE_TYPE_REMOTE_GROUP: + case shareCommon.SHARE_TYPE_FEDERATED, + shareCommon.SHARE_TYPE_FEDERATED_GROUP: return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingRemoteExpireDateDays default: return 0 diff --git a/iOSClient/Share/NCPermissions.swift b/iOSClient/Share/NCPermissions.swift index 1e7d348f49..f9014edd71 100644 --- a/iOSClient/Share/NCPermissions.swift +++ b/iOSClient/Share/NCPermissions.swift @@ -39,7 +39,7 @@ class NCPermissions: NSObject { // permissions - (int) 1 = read; 2 = update; 4 = create; 8 = delete; 16 = share; 31 = all // let permissionReadShare: Int = 1 - let permissionUpdateShare: Int = 2 + let permissionEditShare: Int = 2 let permissionCreateShare: Int = 4 let permissionDeleteShare: Int = 8 let permissionShareShare: Int = 16 @@ -50,47 +50,60 @@ class NCPermissions: NSObject { let permissionMaxFolderShare: Int = 31 let permissionDefaultFileRemoteShareNoSupportShareOption: Int = 3 let permissionDefaultFolderRemoteShareNoSupportShareOption: Int = 15 - // ATTRIBUTES + + // Additional attributes. This also includes the permission to download. + // Check https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#share-attributes let permissionDownloadShare: Int = 0 func isPermissionToRead(_ permission: Int) -> Bool { return ((permission & permissionReadShare) > 0) } + func isPermissionToCanDelete(_ permission: Int) -> Bool { return ((permission & permissionDeleteShare) > 0) } + func isPermissionToCanCreate(_ permission: Int) -> Bool { return ((permission & permissionCreateShare) > 0) } - func isPermissionToCanChange(_ permission: Int) -> Bool { - return ((permission & permissionUpdateShare) > 0) + + func isPermissionToCanEdit(_ permission: Int) -> Bool { + return ((permission & permissionEditShare) > 0) } + func isPermissionToCanShare(_ permission: Int) -> Bool { return ((permission & permissionShareShare) > 0) } + func isAnyPermissionToEdit(_ permission: Int) -> Bool { let canCreate = isPermissionToCanCreate(permission) - let canChange = isPermissionToCanChange(permission) + let canEdit = isPermissionToCanEdit(permission) let canDelete = isPermissionToCanDelete(permission) - return canCreate || canChange || canDelete + return canCreate || canEdit || canDelete } - func isPermissionToReadCreateUpdate(_ permission: Int) -> Bool { + + /// "Can edit" means it has can read, create, edit, and delete. + func canEdit(_ permission: Int, isDirectory: Bool) -> Bool { let canRead = isPermissionToRead(permission) - let canCreate = isPermissionToCanCreate(permission) - let canChange = isPermissionToCanChange(permission) - return canCreate && canChange && canRead + let canCreate = isDirectory ? isPermissionToCanCreate(permission) : true + let canEdit = isPermissionToCanEdit(permission) + let canDelete = isDirectory ? isPermissionToCanDelete(permission) : true + return canCreate && canEdit && canRead && canDelete } - func getPermission(canEdit: Bool, canCreate: Bool, canChange: Bool, canDelete: Bool, canShare: Bool, isDirectory: Bool) -> Int { - var permission = permissionReadShare - if canEdit && !isDirectory { - permission = permission + permissionUpdateShare + /// Read permission is always true for a share, hence why it's not here. + func getPermissionValue(canRead: Bool = true, canCreate: Bool, canEdit: Bool, canDelete: Bool, canShare: Bool, isDirectory: Bool) -> Int { + var permission = 0 + + if canRead { + permission = permission + permissionReadShare } + if canCreate && isDirectory { permission = permission + permissionCreateShare } - if canChange && isDirectory { - permission = permission + permissionUpdateShare + if canEdit { + permission = permission + permissionEditShare } if canDelete && isDirectory { permission = permission + permissionDeleteShare @@ -98,6 +111,7 @@ class NCPermissions: NSObject { if canShare { permission = permission + permissionShareShare } + return permission } } diff --git a/iOSClient/Share/NCShare+NCCellDelegate.swift b/iOSClient/Share/NCShare+NCCellDelegate.swift index a596d5eb8e..135bd468a4 100644 --- a/iOSClient/Share/NCShare+NCCellDelegate.swift +++ b/iOSClient/Share/NCShare+NCCellDelegate.swift @@ -61,9 +61,7 @@ extension NCShare: NCShareLinkCellDelegate, NCShareUserCellDelegate { } func quickStatus(with tableShare: tableShare?, sender: Any) { - guard let tableShare, - let metadata, - tableShare.shareType != NCPermissions().permissionDefaultFileRemoteShareNoSupportShareOption else { return } - self.toggleUserPermissionMenu(isDirectory: metadata.directory, tableShare: tableShare, sender: sender) + guard let tableShare, let metadata else { return } + self.toggleQuickPermissionsMenu(isDirectory: metadata.directory, share: tableShare, sender: sender) } } diff --git a/iOSClient/Share/NCShare.swift b/iOSClient/Share/NCShare.swift index 9872f61be5..7012f5b207 100644 --- a/iOSClient/Share/NCShare.swift +++ b/iOSClient/Share/NCShare.swift @@ -45,13 +45,14 @@ class NCShare: UIViewController, NCSharePagingContent { weak var appDelegate = UIApplication.shared.delegate as? AppDelegate public var metadata: tableMetadata! - public var sharingEnabled = true public var height: CGFloat = 0 let shareCommon = NCShareCommon() let utilityFileSystem = NCUtilityFileSystem() let utility = NCUtility() let database = NCManageDatabase.shared + var shareLinksCount = 0 + var canReshare: Bool { return ((metadata.sharePermissionsCollaborationServices & NCPermissions().permissionShareShare) != 0) } @@ -105,10 +106,8 @@ class NCShare: UIViewController, NCSharePagingContent { reloadData() networking = NCShareNetworking(metadata: metadata, view: self.view, delegate: self, session: session) - if sharingEnabled { - let isVisible = (self.navigationController?.topViewController as? NCSharePaging)?.page == .sharing - networking?.readShare(showLoadingIndicator: isVisible) - } + let isVisible = (self.navigationController?.topViewController as? NCSharePaging)?.page == .sharing + networking?.readShare(showLoadingIndicator: isVisible) searchField.searchTextField.font = .systemFont(ofSize: 14) searchField.delegate = self @@ -134,6 +133,7 @@ class NCShare: UIViewController, NCSharePagingContent { searchField.isUserInteractionEnabled = false searchField.alpha = 0.5 searchField.placeholder = NSLocalizedString("_share_reshare_disabled_", comment: "") + btnContact.isEnabled = false } searchFieldTopConstraint.constant = 45 @@ -163,11 +163,14 @@ class NCShare: UIViewController, NCSharePagingContent { if error == .success, let etag = etag, let imageAvatar = imageAvatar { self.database.addAvatar(fileName: fileName, etag: etag) self.sharedWithYouByImage.image = imageAvatar + self.reloadData() } else if error.errorCode == NCGlobal.shared.errorNotModified, let imageAvatar = self.database.setAvatarLoaded(fileName: fileName) { self.sharedWithYouByImage.image = imageAvatar } } } + + reloadData() } // MARK: - Notification Center @@ -180,6 +183,7 @@ class NCShare: UIViewController, NCSharePagingContent { @objc func reloadData() { shares = self.database.getTableShares(metadata: metadata) + shareLinksCount = 0 tableView.reloadData() } @@ -221,18 +225,20 @@ class NCShare: UIViewController, NCSharePagingContent { extension NCShare: NCShareNetworkingDelegate { func readShareCompleted() { NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) + reloadData() } func shareCompleted() { NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) + reloadData() } func unShareCompleted() { - self.reloadData() + reloadData() } func updateShareWithError(idShare: Int) { - self.reloadData() + reloadData() } func getSharees(sharees: [NKSharee]?) { @@ -254,11 +260,18 @@ extension NCShare: NCShareNetworkingDelegate { appearance.animationduration = 0.25 appearance.textColor = .darkGray + let account = NCManageDatabase.shared.getTableAccount(account: metadata.account) + let existingShares = NCManageDatabase.shared.getTableShares(metadata: metadata) + for sharee in sharees { + if sharee.shareWith == account?.user { continue } // do not show your own account + if let shares = existingShares.share, shares.contains(where: {$0.shareWith == sharee.shareWith}) { continue } // do not show already existing sharees + if metadata.ownerDisplayName == sharee.shareWith { continue } // do not show owner of the share var label = sharee.label if sharee.shareType == shareCommon.SHARE_TYPE_CIRCLE { label += " (\(sharee.circleInfo), \(sharee.circleOwner))" } + dropDown.dataSource.append(label) } @@ -305,10 +318,6 @@ extension NCShare: NCShareNetworkingDelegate { extension NCShare: UITableViewDelegate { func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - if indexPath.section == 0, indexPath.row == 0 { - // internal cell has description - return 40 - } return 60 } } @@ -343,25 +352,29 @@ extension NCShare: UITableViewDataSource { if metadata.e2eEncrypted, NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV12 { cell.tableShare = shares.firstShareLink } else { - if indexPath.row == 1 { + if indexPath.row == 0 { cell.isInternalLink = true } else if shares.firstShareLink?.isInvalidated != true { cell.tableShare = shares.firstShareLink } } + cell.isDirectory = metadata.directory cell.setupCellUI() + shareLinksCount += 1 return cell } guard let tableShare = shares.share?[indexPath.row] else { return UITableViewCell() } - // LINK - if tableShare.shareType == shareCommon.SHARE_TYPE_LINK { + // LINK, EMAIL + if tableShare.shareType == shareCommon.SHARE_TYPE_LINK || tableShare.shareType == shareCommon.SHARE_TYPE_EMAIL { if let cell = tableView.dequeueReusableCell(withIdentifier: "cellLink", for: indexPath) as? NCShareLinkCell { cell.indexPath = indexPath cell.tableShare = tableShare + cell.isDirectory = metadata.directory cell.delegate = self - cell.setupCellUI() + cell.setupCellUI(titleAppendString: String(shareLinksCount)) + if tableShare.shareType == shareCommon.SHARE_TYPE_LINK { shareLinksCount += 1 } return cell } } else { @@ -369,22 +382,9 @@ extension NCShare: UITableViewDataSource { if let cell = tableView.dequeueReusableCell(withIdentifier: "cellUser", for: indexPath) as? NCShareUserCell { cell.indexPath = indexPath cell.tableShare = tableShare + cell.isDirectory = metadata.directory cell.delegate = self - cell.setupCellUI(userId: session.userId) - - let fileName = NCSession.shared.getFileName(urlBase: session.urlBase, user: tableShare.shareWith) - let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) - - if results.image == nil { - cell.fileAvatarImageView?.image = utility.loadUserImage(for: tableShare.shareWith, displayName: tableShare.shareWithDisplayname, urlBase: metadata.urlBase) - } else { - cell.fileAvatarImageView?.image = results.image - } - - if !(results.tblAvatar?.loaded ?? false), - 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: tableView)) - } + cell.setupCellUI(userId: session.userId, session: session, metadata: metadata) return cell } @@ -455,3 +455,5 @@ extension NCShare: UISearchBarDelegate { networking?.getSharees(searchString: searchString) } } + + diff --git a/iOSClient/Share/NCShareCommon.swift b/iOSClient/Share/NCShareCommon.swift index da2f6e1189..5f38d26281 100644 --- a/iOSClient/Share/NCShareCommon.swift +++ b/iOSClient/Share/NCShareCommon.swift @@ -31,10 +31,10 @@ class NCShareCommon: NSObject { let SHARE_TYPE_LINK = 3 let SHARE_TYPE_EMAIL = 4 let SHARE_TYPE_CONTACT = 5 - let SHARE_TYPE_REMOTE = 6 + let SHARE_TYPE_FEDERATED = 6 let SHARE_TYPE_CIRCLE = 7 let SHARE_TYPE_GUEST = 8 - let SHARE_TYPE_REMOTE_GROUP = 9 + let SHARE_TYPE_FEDERATED_GROUP = 9 let SHARE_TYPE_ROOM = 10 // swiftlint:enable identifier_name @@ -82,13 +82,13 @@ class NCShareCommon: NSObject { return UIImage(named: "shareTypeEmail")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) case self.SHARE_TYPE_CONTACT: return UIImage(named: "shareTypeUser")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) - case self.SHARE_TYPE_REMOTE: + case self.SHARE_TYPE_FEDERATED: return UIImage(named: "shareTypeUser")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) case self.SHARE_TYPE_CIRCLE: return UIImage(named: "shareTypeCircles")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) case self.SHARE_TYPE_GUEST: return UIImage(named: "shareTypeUser")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) - case self.SHARE_TYPE_REMOTE_GROUP: + case self.SHARE_TYPE_FEDERATED_GROUP: return UIImage(named: "shareTypeGroup")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) case self.SHARE_TYPE_ROOM: return UIImage(named: "shareTypeRoom")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) diff --git a/iOSClient/Share/NCShareLinkCell.swift b/iOSClient/Share/NCShareLinkCell.swift index 1db528ce16..60787ab5fd 100644 --- a/iOSClient/Share/NCShareLinkCell.swift +++ b/iOSClient/Share/NCShareLinkCell.swift @@ -23,17 +23,22 @@ import UIKit class NCShareLinkCell: UITableViewCell { - @IBOutlet private weak var imageItem: UIImageView! @IBOutlet private weak var labelTitle: UILabel! @IBOutlet private weak var descriptionLabel: UILabel! + @IBOutlet weak var labelQuickStatus: UILabel! + @IBOutlet weak var statusStackView: UIStackView! @IBOutlet private 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? var isInternalLink = false var indexPath = IndexPath() + let utility = NCUtility() override func prepareForReuse() { super.prepareForReuse() @@ -41,12 +46,14 @@ class NCShareLinkCell: UITableViewCell { tableShare = nil } - func setupCellUI() { + func setupCellUI(titleAppendString: String? = nil) { var menuImageName = "ellipsis" + let permissions = NCPermissions() menuButton.isHidden = isInternalLink descriptionLabel.isHidden = !isInternalLink copyButton.isHidden = !isInternalLink && tableShare == nil + statusStackView.isHidden = isInternalLink if #available(iOS 18.0, *) { // use NCShareLinkCell image } else { @@ -63,6 +70,11 @@ class NCShareLinkCell: UITableViewCell { imageItem.image = NCUtility().loadImage(named: "square.and.arrow.up.circle.fill", colors: [NCBrandColor.shared.iconImageColor2]) } else { labelTitle.text = NSLocalizedString("_share_link_", comment: "") + + if let titleAppendString { + labelTitle.text?.append(" (\(titleAppendString))") + } + if let tableShare = tableShare { if !tableShare.label.isEmpty { labelTitle.text? += " (\(tableShare.label))" @@ -78,6 +90,32 @@ class NCShareLinkCell: UITableViewCell { } labelTitle.textColor = NCBrandColor.shared.textColor + + statusStackView.isHidden = true + + if let tableShare { + statusStackView.isHidden = false + labelQuickStatus.text = NSLocalizedString("_custom_permissions_", comment: "") + + if permissions.canEdit(tableShare.permissions, isDirectory: isDirectory) { // Can edit + labelQuickStatus.text = NSLocalizedString("_share_editing_", comment: "") + } + if permissions.getPermissionValue(canRead: false, canCreate: true, canEdit: false, canDelete: false, canShare: false, isDirectory: isDirectory) == tableShare.permissions { + labelQuickStatus.text = NSLocalizedString("_share_file_drop_", comment: "") + } + if permissions.getPermissionValue(canCreate: false, canEdit: false, canDelete: false, canShare: true, isDirectory: isDirectory) == tableShare.permissions { // Read only + labelQuickStatus.text = NSLocalizedString("_share_read_only_", comment: "") + } + + if tableShare.shareType == NCShareCommon().SHARE_TYPE_EMAIL { + labelTitle.text = tableShare.shareWithDisplayname + imageItem.image = NCUtility().loadImage(named: "envelope.circle.fill", colors: [NCBrandColor.shared.getElement(account: tableShare.account)]) + } + } + + 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]) } @IBAction func touchUpCopy(_ sender: Any) { @@ -87,9 +125,14 @@ class NCShareLinkCell: UITableViewCell { @IBAction func touchUpMenu(_ sender: Any) { delegate?.tapMenu(with: tableShare, sender: sender) } + + @objc func openQuickStatus(_ sender: UITapGestureRecognizer) { + delegate?.quickStatus(with: tableShare, sender: 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) } diff --git a/iOSClient/Share/NCShareLinkCell.xib b/iOSClient/Share/NCShareLinkCell.xib index caf3d7dc2e..d5810a5294 100755 --- a/iOSClient/Share/NCShareLinkCell.xib +++ b/iOSClient/Share/NCShareLinkCell.xib @@ -1,6 +1,6 @@ - + @@ -19,7 +19,7 @@ - + @@ -28,7 +28,7 @@ - + - - + + - - + - + + + + + @@ -118,10 +140,10 @@ - + - + diff --git a/iOSClient/Share/NCShareNetworking.swift b/iOSClient/Share/NCShareNetworking.swift index bcf2c69e64..8636dd3d8b 100644 --- a/iOSClient/Share/NCShareNetworking.swift +++ b/iOSClient/Share/NCShareNetworking.swift @@ -84,8 +84,11 @@ class NCShareNetworking: NSObject { } Task { - try await self.readDownloadLimits(account: account, tokens: shares.map(\.token)) - self.delegate?.readShareCompleted() + try? await self.readDownloadLimits(account: account, tokens: shares.map(\.token)) + + Task { @MainActor in + self.delegate?.readShareCompleted() + } } } } else { @@ -191,6 +194,8 @@ class NCShareNetworking: NSObject { /// Remove the download limit on the share, if existent. /// func removeShareDownloadLimit(token: String) { + if !NCCapabilities().getCapabilities(account: metadata.account).capabilityFileSharingDownloadLimit { return } + NCActivityIndicator.shared.start(backgroundView: view) NextcloudKit.shared.removeShareDownloadLimit(account: metadata.account, token: token) { error in @@ -210,6 +215,8 @@ class NCShareNetworking: NSObject { /// - Parameter limit: The new download limit to set. /// func setShareDownloadLimit(_ limit: Int, token: String) { + if !NCCapabilities().getCapabilities(account: metadata.account).capabilityFileSharingDownloadLimit { return } + NCActivityIndicator.shared.start(backgroundView: view) NextcloudKit.shared.setShareDownloadLimit(account: metadata.account, token: token, limit: limit) { error in diff --git a/iOSClient/Share/NCSharePaging.swift b/iOSClient/Share/NCSharePaging.swift index a8951fe869..c8a7ec99e3 100644 --- a/iOSClient/Share/NCSharePaging.swift +++ b/iOSClient/Share/NCSharePaging.swift @@ -225,14 +225,9 @@ extension NCSharePaging: PagingViewControllerDataSource { // MARK: - Header class NCShareHeaderViewController: PagingViewController { - public var image: UIImage? public var metadata = tableMetadata() - public var activityEnabled = true - public var commentsEnabled = true - public var sharingEnabled = true - override func loadView() { view = NCSharePagingView( options: options, diff --git a/iOSClient/Share/NCShareUserCell.swift b/iOSClient/Share/NCShareUserCell.swift index 2eae3f5e98..e4fe84e1e1 100644 --- a/iOSClient/Share/NCShareUserCell.swift +++ b/iOSClient/Share/NCShareUserCell.swift @@ -38,6 +38,7 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { private var index = IndexPath() var tableShare: tableShare? + var isDirectory = false let utility = NCUtility() weak var delegate: NCShareUserCellDelegate? @@ -53,7 +54,7 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { set {} } - func setupCellUI(userId: String) { + func setupCellUI(userId: String, session: NCSession.Session, metadata: tableMetadata) { guard let tableShare = tableShare else { return } @@ -62,7 +63,14 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { target: self, selector: #selector(tapAvatarImage(_:)))] let permissions = NCPermissions() - labelTitle.text = tableShare.shareWithDisplayname + labelTitle.text = (tableShare.shareWithDisplayname.isEmpty ? tableShare.shareWith : tableShare.shareWithDisplayname) + + let type = getType(tableShare) + if !type.isEmpty { + labelTitle.text?.append(" (\(type))") + } + + labelTitle.lineBreakMode = .byTruncatingMiddle labelTitle.textColor = NCBrandColor.shared.textColor isUserInteractionEnabled = true labelQuickStatus.isHidden = false @@ -87,15 +95,41 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { btnQuickStatus.setTitle("", for: .normal) btnQuickStatus.contentHorizontalAlignment = .left - if tableShare.permissions == permissions.permissionCreateShare { - labelQuickStatus.text = NSLocalizedString("_share_file_drop_", comment: "") + if permissions.canEdit(tableShare.permissions, isDirectory: isDirectory) { // Can edit + labelQuickStatus.text = NSLocalizedString("_share_editing_", comment: "") + } else if tableShare.permissions == permissions.permissionReadShare { // Read only + labelQuickStatus.text = NSLocalizedString("_share_read_only_", comment: "") + } else { // Custom permissions + labelQuickStatus.text = NSLocalizedString("_custom_permissions_", comment: "") + } + + let fileName = NCSession.shared.getFileName(urlBase: session.urlBase, user: tableShare.shareWith) + let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) + + imageItem.contentMode = .scaleAspectFill + + if tableShare.shareType == NCShareCommon().SHARE_TYPE_CIRCLE { + imageItem.image = utility.loadImage(named: "person.3.circle.fill", colors: [NCBrandColor.shared.iconImageColor]) + } else if results.image == nil { + imageItem.image = utility.loadUserImage(for: tableShare.shareWith, displayName: tableShare.shareWithDisplayname, urlBase: metadata.urlBase) } else { - // Read Only - if permissions.isAnyPermissionToEdit(tableShare.permissions) { - labelQuickStatus.text = NSLocalizedString("_share_editing_", comment: "") - } else { - labelQuickStatus.text = NSLocalizedString("_share_read_only_", comment: "") - } + imageItem.image = results.image + } + + if !(results.tblAvatar?.loaded ?? false), + 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)) + } + } + + private func getType(_ tableShare: tableShareV2) -> String { + switch tableShare.shareType { + case NCShareCommon().SHARE_TYPE_FEDERATED: + return NSLocalizedString("_remote_", comment: "") + case NCShareCommon().SHARE_TYPE_ROOM: + return NSLocalizedString("_conversation_", comment: "") + default: + return "" } } diff --git a/iOSClient/Share/NCShareUserCell.xib b/iOSClient/Share/NCShareUserCell.xib index df97e61950..eb9b65ca76 100755 --- a/iOSClient/Share/NCShareUserCell.xib +++ b/iOSClient/Share/NCShareUserCell.xib @@ -1,9 +1,9 @@ - + - + @@ -19,10 +19,10 @@ - + - - + + @@ -36,7 +36,7 @@ - + @@ -103,7 +103,7 @@ - + @@ -149,10 +149,10 @@ - + - + diff --git a/iOSClient/Share/Shareable.swift b/iOSClient/Share/Shareable.swift index bbcad82c71..2425abdbad 100644 --- a/iOSClient/Share/Shareable.swift +++ b/iOSClient/Share/Shareable.swift @@ -16,6 +16,7 @@ protocol Shareable: AnyObject { var password: String { get set } var label: String { get set } var note: String { get set } + var downloadAndSync: Bool { get set } var expirationDate: NSDate? { get set } var shareWithDisplayname: String { get set } var attributes: String? { get set } @@ -54,8 +55,44 @@ extension Shareable { // MARK: - tableShare Extension -extension tableShare: Shareable {} +extension tableShare: Shareable { + var downloadAndSync: Bool { + get { + NCManageDatabase.shared.isAttributeDownloadEnabled(attributes: attributes) + } + set { + attributes = NCManageDatabase.shared.setAttibuteDownload(state: newValue) + } + } +} // MARK: - NKShare Extension -extension NKShare: Shareable {} +extension NKShare: Shareable { + var downloadAndSync: Bool { + get { + NCManageDatabase.shared.isAttributeDownloadEnabled(attributes: attributes) + } + set { + attributes = NCManageDatabase.shared.setAttibuteDownload(state: newValue) + } + } +} + +private func isAttributeDownloadEnabled(attributes: String?) -> Bool { + if let attributes = attributes, let data = attributes.data(using: .utf8) { + do { + if let json = try JSONSerialization.jsonObject(with: data) as? [Dictionary] { + for sub in json { + let key = sub["key"] as? String + let enabled = sub["enabled"] as? Bool + let scope = sub["scope"] as? String + if key == "download", scope == "permissions", let enabled = enabled { + return enabled + } + } + } + } catch let error as NSError { print(error) } + } + return true +} diff --git a/iOSClient/Share/TransientShare.swift b/iOSClient/Share/TransientShare.swift index e332bdc927..ece4563028 100644 --- a/iOSClient/Share/TransientShare.swift +++ b/iOSClient/Share/TransientShare.swift @@ -10,6 +10,7 @@ import NextcloudKit /// The persisted counterpart is ``tableShare``. /// class TransientShare: Shareable { + var shareType: Int var permissions: Int @@ -22,6 +23,7 @@ class TransientShare: Shareable { var note: String = "" var expirationDate: NSDate? var shareWithDisplayname: String = "" + var downloadAndSync = false var attributes: String? diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index e017fc907a..f41af7dae8 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -231,6 +231,7 @@ "_select_offline_warning_" = "Making multiple files and folders available offline may take a while and use a lot of memory while doing so."; "_advanced_" = "Advanced"; "_permissions_" = "Permissions"; +"_custom_permissions_" = "Custom permissions"; "_disable_files_app_" = "Disable Files App integration"; "_disable_files_app_footer_" = "Do not permit the access of files via the iOS Files application."; "_time_remaining_" = "%@ remaining"; @@ -382,7 +383,7 @@ "_share_read_only_" = "View only"; "_share_editing_" = "Can edit"; "_share_allow_upload_" = "Allow upload and editing"; -"_share_file_drop_" = "File drop (upload only)"; +"_share_file_drop_" = "File request"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; "_share_hide_download_" = "Hide download"; "_share_limit_download_" = "Limit downloads"; @@ -391,16 +392,19 @@ "_share_expiration_date_" = "Set expiration date"; "_share_note_recipient_" = "Note to recipient"; "_share_add_sharelink_" = "Add another link"; -"_share_can_reshare_" = "Allow resharing"; -"_share_can_create_" = "Allow creating"; -"_share_can_change_" = "Allow editing"; -"_share_can_delete_" = "Allow deleting"; -"_share_can_download_" = "Allow download"; -"_share_unshare_" = "Unshare"; +"_share_can_read_" = "Read"; +"_share_can_reshare_" = "Share"; +"_share_can_create_" = "Create"; +"_share_can_change_" = "Edit"; +"_share_can_delete_" = "Delete"; +"_share_can_download_" = "Allow download and sync"; +"_share_unshare_" = "Delete share"; "_share_internal_link_" = "Internal link"; "_share_internal_link_des_" = "Only works for users with access to this file/folder."; "_share_reshare_disabled_" = "You are not allowed to reshare this file/folder."; "_share_reshare_restricted_" = "Note: You only have limited permission to reshare this file/folder."; +"_remote_" = "Remote"; +"_conversation_" = "Conversation"; "_no_transfer_" = "No transfers yet"; "_no_transfer_sub_" = "Uploads and downloads from this device will show up here";