From 3b544e9fce181ba3f67471255663d82b1cc3810b Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Mon, 27 Nov 2023 17:19:30 +0530 Subject: [PATCH 001/177] NMC 1997 - Sharing customisation NMC 1997 - Merging conflict resolve after nextcloud version 4.9.2 release NMC 1992 - Remove configuration changes NMC 1992 - Removed unused files NMC 1992 - Sharing changes update after NC 5.2 NMC 1992 - Removed project configuration file changes --- Tests/NextcloudUnitTests/SharingTest.swift | 232 +++++ .../Extensions/DateFormatter+Extension.swift | 9 + iOSClient/Extensions/UIButton+Extension.swift | 23 +- .../Extensions/UIToolbar+Extension.swift | 14 + iOSClient/Extensions/UIView+Extension.swift | 22 + iOSClient/Menu/NCShare+Menu.swift | 93 +- iOSClient/NCGlobal.swift | 9 + iOSClient/Networking/NCDownloadAction.swift | 11 +- .../Scan document/PasswordInputField.swift | 75 ++ .../Scan document/PasswordInputField.xib | 70 ++ .../Share/Advanced/NCFilePermissionCell.swift | 91 ++ .../Share/Advanced/NCFilePermissionCell.xib | 95 ++ .../Advanced/NCFilePermissionEditCell.swift | 176 ++++ .../Advanced/NCFilePermissionEditCell.xib | 108 +++ .../Advanced/NCShareAdvancePermission.swift | 814 ++++++++++++++++-- .../NCShareAdvancePermissionFooter.swift | 18 +- .../NCShareAdvancePermissionFooter.xib | 79 +- .../NCShareAdvancePermissionHeader.xib | 84 ++ .../Advanced/NCShareHeaderCustomCell.swift | 30 + .../Advanced/NCShareHeaderCustomCell.xib | 42 + .../Advanced/NCShareNewUserAddComment.swift | 115 ++- .../Share/Advanced/NCShareTextInputCell.swift | 151 ++++ .../Share/Advanced/NCShareTextInputCell.xib | 80 ++ iOSClient/Share/NCSearchUserDropDownCell.xib | 10 +- iOSClient/Share/NCShare+Helper.swift | 113 +++ iOSClient/Share/NCShare+NCCellDelegate.swift | 6 +- iOSClient/Share/NCShare.storyboard | 392 ++++----- iOSClient/Share/NCShare.swift | 368 ++++++-- iOSClient/Share/NCShareCommon.swift | 69 +- iOSClient/Share/NCShareEmailFieldCell.swift | 89 ++ iOSClient/Share/NCShareEmailFieldCell.xib | 128 +++ iOSClient/Share/NCShareHeader.swift | 71 +- iOSClient/Share/NCShareHeaderView.xib | 247 +++--- iOSClient/Share/NCShareLinkCell.swift | 71 +- iOSClient/Share/NCShareNetworking.swift | 15 + iOSClient/Share/NCSharePaging.swift | 133 +-- iOSClient/Share/NCShareUserCell.swift | 55 +- .../Share/ShareDownloadLimitNetwork.swift | 146 ++++ iOSClient/Utility/NCUtility.swift | 7 + 39 files changed, 3616 insertions(+), 745 deletions(-) create mode 100644 Tests/NextcloudUnitTests/SharingTest.swift create mode 100644 iOSClient/Scan document/PasswordInputField.swift create mode 100644 iOSClient/Scan document/PasswordInputField.xib create mode 100644 iOSClient/Share/Advanced/NCFilePermissionCell.swift create mode 100644 iOSClient/Share/Advanced/NCFilePermissionCell.xib create mode 100644 iOSClient/Share/Advanced/NCFilePermissionEditCell.swift create mode 100644 iOSClient/Share/Advanced/NCFilePermissionEditCell.xib create mode 100644 iOSClient/Share/Advanced/NCShareAdvancePermissionHeader.xib create mode 100644 iOSClient/Share/Advanced/NCShareHeaderCustomCell.swift create mode 100644 iOSClient/Share/Advanced/NCShareHeaderCustomCell.xib create mode 100644 iOSClient/Share/Advanced/NCShareTextInputCell.swift create mode 100644 iOSClient/Share/Advanced/NCShareTextInputCell.xib create mode 100644 iOSClient/Share/NCShare+Helper.swift create mode 100644 iOSClient/Share/NCShareEmailFieldCell.swift create mode 100644 iOSClient/Share/NCShareEmailFieldCell.xib create mode 100644 iOSClient/Share/ShareDownloadLimitNetwork.swift diff --git a/Tests/NextcloudUnitTests/SharingTest.swift b/Tests/NextcloudUnitTests/SharingTest.swift new file mode 100644 index 0000000000..66d94dbfbb --- /dev/null +++ b/Tests/NextcloudUnitTests/SharingTest.swift @@ -0,0 +1,232 @@ +// +// SharingTest.swift +// NextcloudTests +// +// Created by A200020526 on 07/06/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import XCTest +@testable import Nextcloud +final class SharingTest: XCTestCase { + + var button: UIButton? + var ncShare: NCShare? + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + super.setUp() + button = UIButton() + ncShare = NCShare() + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + button = nil + ncShare = nil + super.tearDown() + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + + + //Date exntesion test case + func testTomorrow() { + let tomorrow = Date.tomorrow + let expectedTomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date())! + XCTAssertEqual(tomorrow.extendedIso8601String, expectedTomorrow.extendedIso8601String, "Tomorrow date should be correct.") + } + func testToday() { + let today = Date.today + let currentDate = Date() + XCTAssertEqual(today.extendedIso8601String, currentDate.extendedIso8601String, "Today date should be correct.") + } + + func testDayAfter() { + let date = Date() + let dayAfter = date.dayAfter + let expectedDayAfter = Calendar.current.date(byAdding: .day, value: 1, to: date)! + XCTAssertEqual(dayAfter.extendedIso8601String, expectedDayAfter.extendedIso8601String, "Day after date should be correct.") + } + + //Date Formatter extension Test Case + func testShareExpDate() { + let dateFormatter = DateFormatter.shareExpDate + + XCTAssertEqual(dateFormatter.formatterBehavior, .behavior10_4, "Formatter behavior should be correct.") + XCTAssertEqual(dateFormatter.dateStyle, .medium, "Date style should be correct.") + XCTAssertEqual(dateFormatter.dateFormat, NCShareAdvancePermission.displayDateFormat, "Date format should be correct.") + } + + //Button Extension test case + func testSetBackgroundColor() { + // Arrange + let color = UIColor.red + let state: UIControl.State = .normal + + // Act + button?.setBackgroundColor(color, for: state) + + // Assert + XCTAssertNotNil(button?.currentBackgroundImage, "Button background image not nil") + } + + func testSetBackgroundColorForDifferentStates() { + // Arrange + + let selectedColor = UIColor.green + + // Act + button?.isSelected = true + button?.setBackgroundColor(selectedColor, for: .selected) + + // Assert + XCTAssertNotNil(button?.currentBackgroundImage, "Button background image not nil") + button?.isSelected = false + XCTAssertNil(button?.currentBackgroundImage,"Button background image will be nil") + button?.isHighlighted = true + XCTAssertNil(button?.currentBackgroundImage, "Button background image will be nil") + } + + //UIView extension shadow test case + func testAddShadowWithLocation() { + // Create a UIView instance + let view = UIView() + + // Set the shadow with bottom location + view.addShadow(location: .bottom, height: 2, color: .red, opacity: 0.4, radius: 2) + + // Verify that the shadow offset is set correctly for the bottom location + let bottomShadowOffset = view.layer.shadowOffset + XCTAssertEqual(bottomShadowOffset, CGSize(width: 0, height: 2), "Shadow offset not set correctly for bottom location") + + // Verify that the shadow color is set correctly + let shadowColor = view.layer.shadowColor + XCTAssertEqual(shadowColor, UIColor.red.cgColor, "Shadow color not set correctly") + + // Verify that the shadow opacity is set correctly + let shadowOpacity = view.layer.shadowOpacity + XCTAssertEqual(shadowOpacity, 0.4, "Shadow opacity not set correctly") + + // Verify that the shadow radius is set correctly + let shadowRadius = view.layer.shadowRadius + XCTAssertEqual(shadowRadius, 2.0, "Shadow radius not set correctly") + } + + func testAddShadowWithOffset() { + // Create a UIView instance + let view = UIView() + + // Set the shadow with a custom offset + view.addShadow(offset: CGSize(width: 0, height: -4), color: .blue, opacity: 0.6, radius: 3) + + // Verify that the shadow offset is set correctly + let shadowOffset = view.layer.shadowOffset + XCTAssertEqual(shadowOffset, CGSize(width: 0, height: -4), "Shadow offset not set correctly") + + // Verify that the shadow color is set correctly + let shadowColor = view.layer.shadowColor + XCTAssertEqual(shadowColor, UIColor.blue.cgColor, "Shadow color not set correctly") + + // Verify that the shadow opacity is set correctly + let shadowOpacity = view.layer.shadowOpacity + XCTAssertEqual(shadowOpacity, 0.6, "Shadow opacity not set correctly") + + // Verify that the shadow radius is set correctly + let shadowRadius = view.layer.shadowRadius + XCTAssertEqual(shadowRadius, 3.0, "Shadow radius not set correctly") + } + + func testAddShadowForLocation() { + // Create a UIView instance + let view = UIView() + + // Add shadow to the bottom + view.addShadow(location: .bottom, color: UIColor.black) + + // Verify that the shadow properties are set correctly for the bottom location + XCTAssertEqual(view.layer.shadowOffset, CGSize(width: 0, height: 2), "Shadow offset not set correctly for bottom location") + XCTAssertEqual(view.layer.shadowColor, UIColor.black.cgColor, "Shadow color not set correctly for bottom location") + XCTAssertEqual(view.layer.shadowOpacity, 0.4, "Shadow opacity not set correctly for bottom location") + XCTAssertEqual(view.layer.shadowRadius, 2.0, "Shadow radius not set correctly for bottom location") + + // Add shadow to the top + view.addShadow(location: .top) + + // Verify that the shadow properties are set correctly for the top location + XCTAssertEqual(view.layer.shadowOffset, CGSize(width: 0, height: -2), "Shadow offset not set correctly for top location") + XCTAssertEqual(view.layer.shadowColor, NCBrandColor.shared.customerDarkGrey.cgColor, "Shadow color not set correctly for top location") + XCTAssertEqual(view.layer.shadowOpacity, 0.4, "Shadow opacity not set correctly for top location") + XCTAssertEqual(view.layer.shadowRadius, 2.0, "Shadow radius not set correctly for top location") + } + + func testAddShadowForOffset() { + // Create a UIView instance + let view = UIView() + + // Add shadow with custom offset + view.addShadow(offset: CGSize(width: 2, height: 2)) + + // Verify that the shadow properties are set correctly for the custom offset + XCTAssertEqual(view.layer.shadowOffset, CGSize(width: 2, height: 2), "Shadow offset not set correctly for custom offset") + XCTAssertEqual(view.layer.shadowColor, UIColor.black.cgColor, "Shadow color not set correctly for custom offset") + XCTAssertEqual(view.layer.shadowOpacity, 0.5, "Shadow opacity not set correctly for custom offset") + XCTAssertEqual(view.layer.shadowRadius, 5.0, "Shadow radius not set correctly for custom offset") + } + + + func testHasUploadPermission() { + // Create an instance of NCShare + let share = NCShare() + + // Define the input parameters + let tableShareWithUploadPermission = tableShare() + tableShareWithUploadPermission.permissions = NCGlobal.shared.permissionMaxFileShare + + let tableShareWithoutUploadPermission = tableShare() + tableShareWithoutUploadPermission.permissions = NCGlobal.shared.permissionReadShare + + // Call the hasUploadPermission function + let hasUploadPermission1 = share.hasUploadPermission(tableShare: tableShareWithUploadPermission) + let hasUploadPermission2 = share.hasUploadPermission(tableShare: tableShareWithoutUploadPermission) + + // Verify the results + XCTAssertTrue(hasUploadPermission1, "hasUploadPermission returned false for a tableShare with upload permission") + XCTAssertFalse(hasUploadPermission2, "hasUploadPermission returned true for a tableShare without upload permission") + } + + func testGetImageShareType() { + let sut = NCShareCommon() // Replace with the actual class containing the getImageShareType function + + // Test case 1: SHARE_TYPE_USER + let shareType1 = sut.SHARE_TYPE_USER + let result1 = sut.getImageShareType(shareType: shareType1) + XCTAssertEqual(result1, UIImage(named: "shareTypeEmail")?.imageColor(NCBrandColor.shared.label)) + + // Test case 2: SHARE_TYPE_GROUP + let shareType2 = sut.SHARE_TYPE_GROUP + let result2 = sut.getImageShareType(shareType: shareType2) + XCTAssertEqual(result2, UIImage(named: "shareTypeGroup")?.imageColor(NCBrandColor.shared.label)) + + // Test case 3: SHARE_TYPE_LINK + let shareType3 = sut.SHARE_TYPE_LINK + let result3 = sut.getImageShareType(shareType: shareType3) + XCTAssertEqual(result3, UIImage(named: "shareTypeLink")?.imageColor(NCBrandColor.shared.label)) + + // Test case 4: SHARE_TYPE_EMAIL (with isDropDown=false) + let shareType4 = sut.SHARE_TYPE_EMAIL + let result4 = sut.getImageShareType(shareType: shareType4) + XCTAssertEqual(result4, UIImage(named: "shareTypeUser")?.imageColor(NCBrandColor.shared.label)) + + // Test case 5: SHARE_TYPE_EMAIL (with isDropDown=true) + let shareType5 = sut.SHARE_TYPE_EMAIL + let isDropDown5 = true + let result5 = sut.getImageShareType(shareType: shareType5, isDropDown: isDropDown5) + XCTAssertEqual(result5, UIImage(named: "email")?.imageColor(NCBrandColor.shared.label)) + } +} diff --git a/iOSClient/Extensions/DateFormatter+Extension.swift b/iOSClient/Extensions/DateFormatter+Extension.swift index dceb24e7e7..5d6a004843 100644 --- a/iOSClient/Extensions/DateFormatter+Extension.swift +++ b/iOSClient/Extensions/DateFormatter+Extension.swift @@ -29,6 +29,15 @@ extension DateFormatter { let dateFormatter = DateFormatter() dateFormatter.formatterBehavior = .behavior10_4 dateFormatter.dateStyle = .medium + dateFormatter.dateFormat = NCShareAdvancePermission.displayDateFormat return dateFormatter }() } + +extension Date { + static var tomorrow: Date { return Date().dayAfter } + static var today: Date {return Date()} + var dayAfter: Date { + return Calendar.current.date(byAdding: .day, value: 1, to: Date())! + } +} diff --git a/iOSClient/Extensions/UIButton+Extension.swift b/iOSClient/Extensions/UIButton+Extension.swift index 70a85857a5..2879bc1ed0 100644 --- a/iOSClient/Extensions/UIButton+Extension.swift +++ b/iOSClient/Extensions/UIButton+Extension.swift @@ -29,12 +29,21 @@ extension UIButton { } func hideSpinnerAndShowButton() { - let spinnerTag = Int(bitPattern: Unmanaged.passUnretained(self).toOpaque()) - let spinner = self.superview?.subviews.first(where: { view -> Bool in - return view.isKind(of: UIActivityIndicatorView.self) && view.tag == spinnerTag - }) + let spinnerTag = Int(bitPattern: Unmanaged.passUnretained(self).toOpaque()) + let spinner = self.superview?.subviews.first(where: { view -> Bool in + return view.isKind(of: UIActivityIndicatorView.self) && view.tag == spinnerTag + }) - spinner?.removeFromSuperview() - self.isHidden = false - } + spinner?.removeFromSuperview() + self.isHidden = false + } + + func setBackgroundColor(_ color: UIColor, for forState: UIControl.State) { + UIGraphicsBeginImageContext(CGSize(width: 1, height: 1)) + UIGraphicsGetCurrentContext()!.setFillColor(color.cgColor) + UIGraphicsGetCurrentContext()!.fill(CGRect(x: 0, y: 0, width: 1, height: 1)) + let colorImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + self.setBackgroundImage(colorImage, for: forState) + } } diff --git a/iOSClient/Extensions/UIToolbar+Extension.swift b/iOSClient/Extensions/UIToolbar+Extension.swift index 3f1f36fcaf..ac403c9791 100644 --- a/iOSClient/Extensions/UIToolbar+Extension.swift +++ b/iOSClient/Extensions/UIToolbar+Extension.swift @@ -58,6 +58,20 @@ extension UIToolbar { ]) return view } + + static func doneToolbar(completion: @escaping () -> Void) -> UIToolbar { + let doneToolbar: UIToolbar = UIToolbar(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50)) + doneToolbar.barStyle = .default + + let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + let done: UIBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: .done) { + completion() + } + let items = [flexSpace, done] + doneToolbar.items = items + doneToolbar.sizeToFit() + return doneToolbar + } } // https://stackoverflow.com/a/67985180/9506784 diff --git a/iOSClient/Extensions/UIView+Extension.swift b/iOSClient/Extensions/UIView+Extension.swift index 9b7b951026..f5fb4cb0fa 100644 --- a/iOSClient/Extensions/UIView+Extension.swift +++ b/iOSClient/Extensions/UIView+Extension.swift @@ -24,6 +24,11 @@ import Foundation import UIKit +enum VerticalLocation: String { + case bottom + case top +} + extension UIView { // Source @@ -80,4 +85,21 @@ extension UIView { } return nil } + + func addShadow(location: VerticalLocation, height: CGFloat = 2, color: UIColor = NCBrandColor.shared.customerDarkGrey, opacity: Float = 0.4, radius: CGFloat = 2) { + switch location { + case .bottom: + addShadow(offset: CGSize(width: 0, height: height), color: color, opacity: opacity, radius: radius) + case .top: + addShadow(offset: CGSize(width: 0, height: -height), color: color, opacity: opacity, radius: radius) + } + } + + func addShadow(offset: CGSize, color: UIColor = .black, opacity: Float = 0.5, radius: CGFloat = 5.0) { + self.layer.masksToBounds = false + self.layer.shadowColor = color.cgColor + self.layer.shadowOffset = offset + self.layer.shadowOpacity = opacity + self.layer.shadowRadius = radius + } } diff --git a/iOSClient/Menu/NCShare+Menu.swift b/iOSClient/Menu/NCShare+Menu.swift index ffabe546bf..69d689d352 100644 --- a/iOSClient/Menu/NCShare+Menu.swift +++ b/iOSClient/Menu/NCShare+Menu.swift @@ -28,6 +28,7 @@ import NextcloudKit extension NCShare { func toggleShareMenu(for share: tableShare, sender: Any?) { let capabilities = NKCapabilities.shared.getCapabilitiesBlocking(for: self.metadata.account) +// func toggleShareMenu(for share: tableShare, sendMail: Bool, folder: Bool, sender: Any) { var actions = [NCMenuAction]() if share.shareType == NCShareCommon().SHARE_TYPE_LINK, canReshare { @@ -36,8 +37,17 @@ extension NCShare { title: NSLocalizedString("_share_add_sharelink_", comment: ""), icon: utility.loadImage(named: "plus", colors: [NCBrandColor.shared.iconImageColor]), sender: sender, +// func toggleShareMenu(for share: tableShare, sendMail: Bool, folder: Bool, sender: Any) { +// +// var actions = [NCMenuAction]() +// +// if !folder { +// actions.append( +// NCMenuAction( +// title: NSLocalizedString("_open_in_", comment: ""), +// icon: utility.loadImage(named: "viewInFolder").imageColor(NCBrandColor.shared.brandElement), action: { _ in - self.makeNewLinkShare() + NCShareCommon().copyLink(link: share.url, viewController: self, sender: sender) } ) ) @@ -45,16 +55,20 @@ extension NCShare { actions.append( NCMenuAction( - title: NSLocalizedString("_details_", comment: ""), - icon: utility.loadImage(named: "pencil", colors: [NCBrandColor.shared.iconImageColor]), + title: NSLocalizedString("_advance_permissions_", comment: ""), + icon: utility.loadImage(named: "rename").imageColor(NCBrandColor.shared.brandElement), accessibilityIdentifier: "shareMenu/details", sender: sender, +// title: NSLocalizedString("_details_", comment: ""), +// icon: utility.loadImage(named: "pencil", colors: [NCBrandColor.shared.iconImageColor]), +// accessibilityIdentifier: "shareMenu/details", + 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.share = share advancePermission.oldTableShare = tableShare(value: share) advancePermission.metadata = self.metadata @@ -66,12 +80,29 @@ extension NCShare { } ) ) - + + if sendMail { + actions.append( + NCMenuAction( + title: NSLocalizedString("_send_new_email_", comment: ""), + icon: NCUtility().loadImage(named: "email").imageColor(NCBrandColor.shared.brandElement), + action: { menuAction in + let storyboard = UIStoryboard(name: "NCShare", bundle: nil) + guard let viewNewUserComment = storyboard.instantiateViewController(withIdentifier: "NCShareNewUserAddComment") as? NCShareNewUserAddComment else { return } + viewNewUserComment.metadata = self.metadata + viewNewUserComment.share = tableShare(value: share) + viewNewUserComment.networking = self.networking + self.navigationController?.pushViewController(viewNewUserComment, animated: true) + } + ) + ) + } + actions.append( NCMenuAction( title: NSLocalizedString("_share_unshare_", comment: ""), destructive: true, - icon: utility.loadImage(named: "person.2.slash"), + icon: utility.loadImage(named: "trash").imageColor(NCBrandColor.shared.brandElement), sender: sender, action: { _ in Task { @@ -103,6 +134,8 @@ extension NCShare { title: NSLocalizedString("_share_read_only_", comment: ""), icon: utility.loadImage(named: "eye", colors: [NCBrandColor.shared.iconImageColor]), selected: share.permissions == (permissions.permissionReadShare + permissions.permissionShareShare) || share.permissions == permissions.permissionReadShare, +// icon: UIImage(), +// selected: tableShare.permissions == (NCGlobal.shared.permissionReadShare + NCGlobal.shared.permissionShareShare) || tableShare.permissions == NCGlobal.shared.permissionReadShare, on: false, sender: sender, action: { _ in @@ -111,9 +144,12 @@ extension NCShare { } ), NCMenuAction( - title: NSLocalizedString("_share_editing_", comment: ""), +// title: NSLocalizedString("_share_editing_", comment: ""), + title: isDirectory ? NSLocalizedString("_share_allow_upload_", comment: "") : NSLocalizedString("_share_editing_", comment: ""), icon: utility.loadImage(named: "pencil", colors: [NCBrandColor.shared.iconImageColor]), selected: hasUploadPermission(tableShare: share), +// icon: UIImage(), +// selected: hasUploadPermission(tableShare: tableShare), on: false, sender: sender, action: { _ in @@ -143,20 +179,37 @@ extension NCShare { )] ) - 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) +// 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) + if isDirectory, + NCShareCommon().isFileDropOptionVisible(isDirectory: isDirectory, shareType: tableShare.shareType) { + actions.append( + NCMenuAction( + title: NSLocalizedString("_share_file_drop_", comment: ""), + icon: tableShare.permissions == NCGlobal.shared.permissionCreateShare ? UIImage(named: "success")?.image(color: NCBrandColor.shared.customer, size: 25.0) ?? UIImage() : UIImage(), + selected: false, + on: false, + action: { menuAction in + let permissions = NCGlobal.shared.permissionCreateShare + self.updateSharePermissions(share: tableShare, permissions: permissions) + } + ) + ) } - + self.presentMenu(with: actions, sender: sender) } diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index 6ee684f233..7ea0f2bf75 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -270,6 +270,15 @@ final class NCGlobal: Sendable { let notificationCenterEnableSwipeGesture = "enableSwipeGesture" let notificationCenterDisableSwipeGesture = "disableSwipeGesture" + + let notificationCenterShareViewIn = "ShareViewIn" + let notificationCenterShareAdvancePermission = "ShareAdvancePermission" + let notificationCenterShareSendEmail = "ShareSendEmail" + let notificationCenterShareUnshare = "ShareUnshare" + let notificationCenterStatusReadOnly = "statusReadOnly" + let notificationCenterStatusEditing = "statusEditing" + let notificationCenterStatusFileDrop = "statusFileDrop" + let notificationCenterPlayerIsPlaying = "playerIsPlaying" let notificationCenterPlayerStoppedPlaying = "playerStoppedPlaying" diff --git a/iOSClient/Networking/NCDownloadAction.swift b/iOSClient/Networking/NCDownloadAction.swift index 4445ca5f78..3bb2c23f67 100644 --- a/iOSClient/Networking/NCDownloadAction.swift +++ b/iOSClient/Networking/NCDownloadAction.swift @@ -243,7 +243,6 @@ class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSel NCActivityIndicator.shared.stop() if let metadata = metadata, error == .success { - var pages: [NCBrandOptions.NCInfoPagingTab] = [] let shareNavigationController = UIStoryboard(name: "NCShare", bundle: nil).instantiateInitialViewController() as? UINavigationController let shareViewController = shareNavigationController?.topViewController as? NCSharePaging @@ -261,16 +260,8 @@ class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSel (pages, page) = NCApplicationHandle().filterPages(pages: pages, page: page, metadata: metadata) shareViewController?.pages = pages +// let shareViewController = shareNavigationController?.topViewController as? NCShare shareViewController?.metadata = metadata - - if pages.contains(page) { - shareViewController?.page = page - } else if let page = pages.first { - shareViewController?.page = page - } else { - return - } - shareNavigationController?.modalPresentationStyle = .formSheet if let shareNavigationController = shareNavigationController { viewController.present(shareNavigationController, animated: true, completion: nil) diff --git a/iOSClient/Scan document/PasswordInputField.swift b/iOSClient/Scan document/PasswordInputField.swift new file mode 100644 index 0000000000..c2b893324b --- /dev/null +++ b/iOSClient/Scan document/PasswordInputField.swift @@ -0,0 +1,75 @@ +// +// PasswordInputField.swift +// Nextcloud +// +// Created by Sumit on 10/06/21. +// Copyright © 2021 Marino Faggiana. All rights reserved. +// + +import Foundation + +class PasswordInputField: XLFormBaseCell,UITextFieldDelegate { + + @IBOutlet weak var fileNameInputTextField: UITextField! + @IBOutlet weak var separatorBottom: UIView! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + fileNameInputTextField.isSecureTextEntry = true + fileNameInputTextField.delegate = self + separatorBottom.backgroundColor = NCBrandColor.shared.systemGray4 + self.selectionStyle = .none + fileNameInputTextField.inputAccessoryView = UIToolbar.doneToolbar { + self.fileNameInputTextField.resignFirstResponder() + } + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + + override func configure() { + super.configure() + // fileNameInputTextField.isEnabled = false + + } + + override func update() { + super.update() + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + if fileNameInputTextField == textField { + if let rowDescriptor = rowDescriptor, let text = self.fileNameInputTextField.text { + + if (text + " ").isEmpty == false { + rowDescriptor.value = self.fileNameInputTextField.text! + string + } else { + rowDescriptor.value = nil + } + } + } + + self.formViewController().textField(textField, shouldChangeCharactersIn: range, replacementString: string) + + return true + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + self.formViewController()?.textFieldShouldReturn(fileNameInputTextField) + return true + } + + func textFieldShouldClear(_ textField: UITextField) -> Bool { + self.formViewController()?.textFieldShouldClear(fileNameInputTextField) + return true + } + + override class func formDescriptorCellHeight(for rowDescriptor: XLFormRowDescriptor!) -> CGFloat { + return 45 + } +} diff --git a/iOSClient/Scan document/PasswordInputField.xib b/iOSClient/Scan document/PasswordInputField.xib new file mode 100644 index 0000000000..26914e0760 --- /dev/null +++ b/iOSClient/Scan document/PasswordInputField.xib @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Share/Advanced/NCFilePermissionCell.swift b/iOSClient/Share/Advanced/NCFilePermissionCell.swift new file mode 100644 index 0000000000..4e5ec77f72 --- /dev/null +++ b/iOSClient/Share/Advanced/NCFilePermissionCell.swift @@ -0,0 +1,91 @@ +// +// NCFilePermissionCell.swift +// Nextcloud +// +// Created by T-systems on 17/08/21. +// Copyright © 2021 Marino Faggiana. All rights reserved. +// + +import UIKit + +class NCFilePermissionCell: XLFormButtonCell { + + @IBOutlet weak var seperator: UIView! + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var imageCheck: UIImageView! + @IBOutlet weak var seperatorBelow: UIView! + @IBOutlet weak var seperatorBelowFull: UIView! + @IBOutlet weak var titleLabelBottom: UILabel! + + override func awakeFromNib() { + super.awakeFromNib() + self.selectionStyle = .none + self.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + self.titleLabel.textColor = NCBrandColor.shared.label + NotificationCenter.default.addObserver(self, selector: #selector(changeTheming), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeTheming), object: nil) + } + + @objc func changeTheming() { + self.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + self.titleLabel.textColor = NCBrandColor.shared.label + self.titleLabelBottom.textColor = NCBrandColor.shared.iconColor + } + + override func configure() { + super.configure() + } + + + override func update() { + super.update() + self.selectionStyle = .none + if rowDescriptor.tag == "NCFilePermissionCellSharing" || rowDescriptor.tag == "NCFilePermissionCellAdvanceTxt" { + self.seperator.isHidden = true + self.seperatorBelowFull.isHidden = true + self.seperatorBelow.isHidden = true + self.titleLabel.font = UIFont.boldSystemFont(ofSize: 17) + self.titleLabelBottom.font = UIFont.boldSystemFont(ofSize: 17) + } + if rowDescriptor.tag == "kNMCFilePermissionCellEditing" { + self.seperator.isHidden = true +// self.seperatorBelowFull.isHidden = true + } + + if rowDescriptor.tag == "NCFilePermissionCellFileDrop" { + self.seperator.isHidden = true + self.seperatorBelow.isHidden = false + self.seperatorBelowFull.isHidden = true + } + + if rowDescriptor.tag == "kNMCFilePermissionEditCellEditingCanShare" { + self.seperator.isHidden = true + self.seperatorBelowFull.isHidden = false + } + + if rowDescriptor.tag == "kNMCFilePermissionCellEditingMsg" { + self.seperator.isHidden = true + self.seperatorBelow.isHidden = true + self.seperatorBelowFull.isHidden = false + } + + if rowDescriptor.tag == "kNMCFilePermissionCellFiledropMessage" { + self.seperator.isHidden = true + self.seperatorBelow.isHidden = true + self.seperatorBelowFull.isHidden = false + self.imageCheck.isHidden = true + } + } + + @objc func switchChanged(mySwitch: UISwitch) { + self.rowDescriptor.value = mySwitch.isOn + } + + override func formDescriptorCellDidSelected(withForm controller: XLFormViewController!) { + self.selectionStyle = .none + } + + override class func formDescriptorCellHeight(for rowDescriptor: XLFormRowDescriptor!) -> CGFloat { + return 44.0 + } + +} diff --git a/iOSClient/Share/Advanced/NCFilePermissionCell.xib b/iOSClient/Share/Advanced/NCFilePermissionCell.xib new file mode 100644 index 0000000000..e40c22e14d --- /dev/null +++ b/iOSClient/Share/Advanced/NCFilePermissionCell.xib @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Share/Advanced/NCFilePermissionEditCell.swift b/iOSClient/Share/Advanced/NCFilePermissionEditCell.swift new file mode 100644 index 0000000000..b32d66a74f --- /dev/null +++ b/iOSClient/Share/Advanced/NCFilePermissionEditCell.swift @@ -0,0 +1,176 @@ +// +// NCFilePermissionEditCell.swift +// Nextcloud +// +// Created by T-systems on 10/08/21. +// Copyright © 2021 Marino Faggiana. All rights reserved. +// + +import UIKit + +class NCFilePermissionEditCell: XLFormBaseCell, UITextFieldDelegate { + + @IBOutlet weak var seperator: UIView! + @IBOutlet weak var seperatorMiddle: UIView! + @IBOutlet weak var seperatorBottom: UIView! + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var switchControl: UISwitch! + @IBOutlet weak var cellTextField: UITextField! + @IBOutlet weak var buttonLinkLabel: UIButton! + let datePicker = UIDatePicker() + var expirationDateText: String! + var expirationDate: NSDate! + + override func awakeFromNib() { + super.awakeFromNib() + self.cellTextField.delegate = self + self.cellTextField.isEnabled = false + self.selectionStyle = .none + switchControl.addTarget(self, action: #selector(switchChanged), for: UIControl.Event.valueChanged) + self.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + self.titleLabel.textColor = NCBrandColor.shared.label + self.cellTextField.attributedPlaceholder = NSAttributedString(string: "", + attributes: [NSAttributedString.Key.foregroundColor: NCBrandColor.shared.fileFolderName]) + self.cellTextField.textColor = NCBrandColor.shared.singleTitleColorButton + NotificationCenter.default.addObserver(self, selector: #selector(changeTheming), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeTheming), object: nil) + } + + @objc func changeTheming() { + self.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + self.titleLabel.textColor = NCBrandColor.shared.iconColor + } + + override func configure() { + super.configure() + } + + override func update() { + super.update() + + if rowDescriptor.tag == "kNMCFilePermissionCellEditingCanShare" { + self.seperatorMiddle.isHidden = true + self.seperatorBottom.isHidden = true + self.cellTextField.isHidden = true + } + if rowDescriptor.tag == "kNMCFilePermissionEditCellLinkLabel" { + self.switchControl.isHidden = true + self.cellTextField.isEnabled = true + self.seperatorBottom.isHidden = true + } + if rowDescriptor.tag == "kNMCFilePermissionEditCellLinkLabel" { + self.switchControl.isHidden = true + } + + if rowDescriptor.tag == "kNMCFilePermissionEditCellExpiration" { + self.seperator.isHidden = true + setDatePicker(sender: self.cellTextField) + } + + if rowDescriptor.tag == "kNMCFilePermissionEditPasswordCellWithText" { + self.seperatorMiddle.isHidden = true + self.seperator.isHidden = true + } + + if rowDescriptor.tag == "kNMCFilePermissionEditCellHideDownload" { + self.seperator.isHidden = true + self.seperatorMiddle.isHidden = true + } + + if rowDescriptor.tag == "kNMCFilePermissionEditCellEditingCanShare" { + self.seperator.isHidden = true + self.seperatorBottom.isHidden = true + } + } + + @objc func switchChanged(mySwitch: UISwitch) { + let isOn = mySwitch.isOn + if isOn { + //on + self.rowDescriptor.value = isOn + self.cellTextField.isEnabled = true + cellTextField.delegate = self + } else { + self.rowDescriptor.value = isOn + self.cellTextField.isEnabled = false + if rowDescriptor.tag == "kNMCFilePermissionEditCellExpiration" || rowDescriptor.tag == "kNMCFilePermissionEditCellPassword" { + self.cellTextField.text = "" + } + } + if rowDescriptor.tag == "kNMCFilePermissionEditPasswordCellWithText" { + seperatorBottom.isHidden = isOn + seperatorMiddle.isHidden = !isOn + } + if rowDescriptor.tag == "kNMCFilePermissionEditCellExpiration" { + seperatorBottom.isHidden = isOn + seperatorMiddle.isHidden = !isOn + } + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + if self.cellTextField == textField { + if let rowDescriptor = rowDescriptor, let text = self.cellTextField.text { + + if (text + " ").isEmpty == false { + rowDescriptor.value = self.cellTextField.text! + string + } else { + rowDescriptor.value = nil + } + } + } + + self.formViewController().textField(textField, shouldChangeCharactersIn: range, replacementString: string) + return true + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + self.formViewController()?.textFieldShouldReturn(textField) + return true + } + + func textFieldShouldClear(_ textField: UITextField) -> Bool { + self.formViewController()?.textFieldShouldClear(textField) + return true + } + + override class func formDescriptorCellHeight(for rowDescriptor: XLFormRowDescriptor!) -> CGFloat { + return 30 + } + + override func formDescriptorCellDidSelected(withForm controller: XLFormViewController!) { + self.selectionStyle = .none + } + + func setDatePicker(sender: UITextField) { + //Format Date + datePicker.datePickerMode = .date + datePicker.minimumDate = Date() + //ToolBar + let toolbar = UIToolbar(); + toolbar.sizeToFit() + let doneButton = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: .plain, target: self, action: #selector(doneDatePicker)); + let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil) + let cancelButton = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: .plain, target: self, action: #selector(cancelDatePicker)); + + toolbar.setItems([doneButton,spaceButton,cancelButton], animated: false) + + sender.inputAccessoryView = toolbar + sender.inputView = datePicker + } + + @objc func doneDatePicker() { + let dateFormatter = DateFormatter() + dateFormatter.formatterBehavior = .behavior10_4 + dateFormatter.dateStyle = .medium + self.expirationDateText = dateFormatter.string(from: datePicker.date as Date) + + dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss" + self.expirationDate = datePicker.date as NSDate + self.cellTextField.text = self.expirationDateText + self.rowDescriptor.value = self.expirationDate + self.cellTextField.endEditing(true) + } + + @objc func cancelDatePicker() { + self.cellTextField.endEditing(true) + } +} diff --git a/iOSClient/Share/Advanced/NCFilePermissionEditCell.xib b/iOSClient/Share/Advanced/NCFilePermissionEditCell.xib new file mode 100644 index 0000000000..2a7aa3ac63 --- /dev/null +++ b/iOSClient/Share/Advanced/NCFilePermissionEditCell.xib @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift index a8491adf99..31a0d98b53 100644 --- a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift +++ b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift @@ -27,6 +27,46 @@ import SVGKit import CloudKit class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDelegate, NCShareNavigationTitleSetting { +//class NCShareAdvancePermission: XLFormViewController, NCShareAdvanceFotterDelegate, NCShareDetail { + func dismissShareAdvanceView(shouldSave: Bool) { + if shouldSave { + self.oldTableShare?.permissions = self.permission ?? (self.oldTableShare?.permissions ?? 0) + self.share.permissions = self.permission ?? (self.oldTableShare?.permissions ?? 0) + if isNewShare { + let storyboard = UIStoryboard(name: "NCShare", bundle: nil) + guard let viewNewUserComment = storyboard.instantiateViewController(withIdentifier: "NCShareNewUserAddComment") as? NCShareNewUserAddComment else { return } + viewNewUserComment.metadata = self.metadata + viewNewUserComment.share = self.share + viewNewUserComment.networking = self.networking + self.navigationController?.pushViewController(viewNewUserComment, animated: true) + } else { + if let downloadSwitchCell = getDownloadLimitSwitchCell() { + let isDownloadLimitOn = downloadSwitchCell.switchControl.isOn + if !isDownloadLimitOn { + setDownloadLimit(deleteLimit: true, limit: "") + } else { + let downloadLimitInputCell = getDownloadLimitInputCell() + let enteredDownloadLimit = downloadLimitInputCell?.cellTextField.text ?? "" + if enteredDownloadLimit.isEmpty { + showDownloadLimitError(message: NSLocalizedString("_share_download_limit_alert_empty_", comment: "")) + return + } + if let num = Int(enteredDownloadLimit), num < 1 { + showDownloadLimitError(message: NSLocalizedString("_share_download_limit_alert_zero_", comment: "")) + return + } + setDownloadLimit(deleteLimit: false, limit: enteredDownloadLimit) + } + } + + networking?.updateShare(option: share) + navigationController?.popViewController(animated: true) + } + } else { + navigationController?.popViewController(animated: true) + } + } + let database = NCManageDatabase.shared var oldTableShare: tableShare? @@ -55,7 +95,14 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg var shareConfig: NCShareConfig! var networking: NCShareNetworking? - + let tableViewBottomInset: CGFloat = 80.0 + lazy var shareType: Int = { + isNewShare ? share.shareType : oldTableShare?.shareType ?? NCShareCommon().SHARE_TYPE_USER + }() + static let displayDateFormat = "dd. MMM. yyyy" + var downloadLimit: DownloadLimit? + var permission: Int? + override func viewDidLoad() { super.viewDidLoad() self.shareConfig = NCShareConfig(parentMetadata: metadata, share: share) @@ -77,10 +124,19 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg tableView.estimatedRowHeight = tableView.rowHeight tableView.rowHeight = UITableView.automaticDimension + self.setNavigationTitle() - self.navigationItem.hidesBackButton = true - // disable pull to dimiss + // disbale pull to dimiss isModalInPresentation = true + self.tableView.separatorStyle = UITableViewCell.SeparatorStyle.none + self.permission = oldTableShare?.permissions + initializeForm() + changeTheming() + getDownloadLimit() + NotificationCenter.default.addObserver(self, selector: #selector(changeTheming), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeTheming), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) } override func viewWillLayoutSubviews() { @@ -89,32 +145,60 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg setupHeaderView() setupFooterView() } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + if (UIDevice.current.userInterfaceIdiom == .phone), UIDevice().hasNotch { + let isLandscape = UIDevice.current.orientation.isLandscape + let tableViewWidth = isLandscape ? view.bounds.width - 80 : view.bounds.width + tableView.frame = CGRect(x: isLandscape ? 40 : 0, y: tableView.frame.minY, width: tableViewWidth, height: tableView.bounds.height) + tableView.layoutIfNeeded() + } + } + + @objc func keyboardWillShow(_ notification:Notification) { + if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { + tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height + 60, right: 0) + } + } + + @objc func keyboardWillHide(_ notification:Notification) { + if ((notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue) != nil { + tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: tableViewBottomInset, right: 0) + } + } + @objc func changeTheming() { + tableView.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + self.view.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + self.navigationController?.navigationBar.tintColor = NCBrandColor.shared.customer + tableView.reloadData() + } + func setupFooterView() { guard let footerView = (Bundle.main.loadNibNamed("NCShareAdvancePermissionFooter", owner: self, options: nil)?.first as? NCShareAdvancePermissionFooter) else { return } footerView.setupUI(delegate: self, account: metadata.account) // tableFooterView can't use auto layout directly - let container = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 120)) - container.addSubview(footerView) - tableView.tableFooterView = container + footerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 100) + self.view.addSubview(footerView) footerView.translatesAutoresizingMaskIntoConstraints = false - footerView.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true - footerView.heightAnchor.constraint(equalTo: container.heightAnchor).isActive = true - footerView.widthAnchor.constraint(equalTo: container.widthAnchor).isActive = true + footerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + footerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + footerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true + footerView.heightAnchor.constraint(equalToConstant: 100).isActive = true + tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: tableViewBottomInset, right: 0) + } func setupHeaderView() { guard let headerView = (Bundle.main.loadNibNamed("NCShareHeader", owner: self, options: nil)?.first as? NCShareHeader) else { return } headerView.setupUI(with: metadata) - - let container = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 220)) - container.addSubview(headerView) - tableView.tableHeaderView = container + headerView.ocId = metadata.ocId + headerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 190) + self.tableView.tableHeaderView = headerView headerView.translatesAutoresizingMaskIntoConstraints = false - headerView.topAnchor.constraint(equalTo: container.topAnchor).isActive = true - headerView.heightAnchor.constraint(equalTo: container.heightAnchor).isActive = true - headerView.widthAnchor.constraint(equalTo: container.widthAnchor).isActive = true + headerView.heightAnchor.constraint(equalToConstant: 190).isActive = true + headerView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { @@ -148,8 +232,357 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg noteCell.detailTextLabel?.numberOfLines = 0 return noteCell } - if let cell = cell as? NCShareDateCell { cell.onReload = tableView.reloadData } - return cell + } + + func initializeForm() { + let form : XLFormDescriptor + var section : XLFormSectionDescriptor + var row : XLFormRowDescriptor + + form = XLFormDescriptor(title: "Other Cells") + + //Sharing + section = XLFormSectionDescriptor.formSection(withTitle: "") + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFilePermissionCell"] = NCFilePermissionCell.self + row = XLFormRowDescriptor(tag: "NCFilePermissionCellSharing", rowType: "kNMCFilePermissionCell", title: "") + row.cellConfig["titleLabel.text"] = NSLocalizedString("_sharing_", comment: "") + row.height = 44 + section.addFormRow(row) + + //PERMISSION + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCShareHeaderCustomCell"] = NCShareHeaderCustomCell.self + row = XLFormRowDescriptor(tag: "kNMCShareHeaderCustomCell", rowType: "kNMCShareHeaderCustomCell", title: NSLocalizedString("_PERMISSIONS_", comment: "")) + row.height = 26 + row.cellConfig["titleLabel.text"] = NSLocalizedString("_PERMISSIONS_", comment: "") + section.addFormRow(row) + + //read only + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFilePermissionCell"] = NCFilePermissionCell.self + row = XLFormRowDescriptor(tag: "NCFilePermissionCellRead", rowType: "kNMCFilePermissionCell", title: NSLocalizedString("_PERMISSIONS_", comment: "")) + row.cellConfig["titleLabel.text"] = NSLocalizedString("_share_read_only_", comment: "") + row.height = 44 + + if let permission = self.permission, !CCUtility.isAnyPermission(toEdit: permission), permission != NCGlobal.shared.permissionCreateShare { + row.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: NCBrandColor.shared.customer, size: 25.0) + } + if isNewShare { + row.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: NCBrandColor.shared.customer, size: 25.0) + self.permission = NCGlobal.shared.permissionReadShare + } + section.addFormRow(row) + + //editing + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFilePermissionCell"] = NCFilePermissionCell.self + + row = XLFormRowDescriptor(tag: "kNMCFilePermissionCellEditing", rowType: "kNMCFilePermissionCell", title: NSLocalizedString("_PERMISSIONS_", comment: "")) + row.cellConfig["titleLabel.text"] = NSLocalizedString("_share_allow_editing_", comment: "") + row.height = 44 + if let permission = self.permission { + if CCUtility.isAnyPermission(toEdit: permission), permission != NCGlobal.shared.permissionCreateShare { + row.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: NCBrandColor.shared.customer, size: 25.0) + } + } + let enabled = NCShareCommon().isEditingEnabled(isDirectory: metadata.directory, fileExtension: metadata.fileExtension, shareType: shareType) || checkIsCollaboraFile() + row.cellConfig["titleLabel.textColor"] = enabled ? NCBrandColor.shared.label : NCBrandColor.shared.systemGray + row.disabled = !enabled + section.addFormRow(row) + + if !enabled { + row = XLFormRowDescriptor(tag: "kNMCFilePermissionCellEditingMsg", rowType: "kNMCFilePermissionCell", title: NSLocalizedString("_PERMISSIONS_", comment: "")) + row.cellConfig["titleLabel.text"] = NSLocalizedString("share_editing_message", comment: "") + row.cellConfig["titleLabel.textColor"] = NCBrandColor.shared.gray60 + row.height = 80 + section.addFormRow(row) + } + + //file drop + if isFileDropOptionVisible() { + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFilePermissionCell"] = NCFilePermissionCell.self + row = XLFormRowDescriptor(tag: "NCFilePermissionCellFileDrop", rowType: "kNMCFilePermissionCell", title: NSLocalizedString("_PERMISSIONS_", comment: "")) + row.cellConfig["titleLabel.text"] = NSLocalizedString("_share_file_drop_", comment: "") + if self.permission == NCGlobal.shared.permissionCreateShare { + row.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: NCBrandColor.shared.customer, size: 25.0) + } + row.height = 44 + section.addFormRow(row) + + //sammelbox message + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFilePermissionCell"] = NCFilePermissionCell.self + + row = XLFormRowDescriptor(tag: "kNMCFilePermissionCellFiledropMessage", rowType: "kNMCFilePermissionCell", title: NSLocalizedString("_PERMISSIONS_", comment: "")) + row.cellConfig["titleLabel.text"] = NSLocalizedString("_file_drop_message_", comment: "") + row.cellConfig["titleLabel.textColor"] = NCBrandColor.shared.gray60 + row.cellConfig["imageCheck.image"] = UIImage() + row.height = 84 + section.addFormRow(row) + } + + //empty cell + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCXLFormBaseCell"] = NCSeparatorCell.self + row = XLFormRowDescriptor(tag: "kNMCXLFormBaseCell", rowType: "kNMCXLFormBaseCell", title: NSLocalizedString("", comment: "")) + row.height = 16 + section.addFormRow(row) + + //ADVANCE PERMISSION + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFilePermissionCell"] = NCFilePermissionCell.self + + row = XLFormRowDescriptor(tag: "NCFilePermissionCellAdvanceTxt", rowType: "kNMCFilePermissionCell", title: NSLocalizedString("_PERMISSIONS_", comment: "")) + row.cellConfig["titleLabel.text"] = NSLocalizedString("_advance_permissions_", comment: "") + row.height = 52 + section.addFormRow(row) + + if isLinkShare() { + //link label section header + + // Custom Link label + XLFormViewController.cellClassesForRowDescriptorTypes()["kNCShareTextInputCell"] = NCShareTextInputCell.self + row = XLFormRowDescriptor(tag: "kNCShareTextInputCellCustomLinkField", rowType: "kNCShareTextInputCell", title: "") + row.cellConfig["cellTextField.placeholder"] = NSLocalizedString("_custom_link_label", comment: "") + row.cellConfig["cellTextField.text"] = oldTableShare?.label + row.cellConfig["cellTextField.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["cellTextField.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["cellTextField.textColor"] = NCBrandColor.shared.label + row.height = 44 + section.addFormRow(row) + } + + //can reshare + if isCanReshareOptionVisible() { + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFilePermissionEditCell"] = NCFilePermissionEditCell.self + + row = XLFormRowDescriptor(tag: "kNMCFilePermissionEditCellEditingCanShare", rowType: "kNMCFilePermissionEditCell", title: "") + row.cellConfig["switchControl.onTintColor"] = NCBrandColor.shared.customer + row.cellClass = NCFilePermissionEditCell.self + row.cellConfig["titleLabel.text"] = NSLocalizedString("_share_can_reshare_", comment: "") + row.height = 44 + section.addFormRow(row) + } + + //hide download + if isHideDownloadOptionVisible() { + + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFilePermissionEditCell"] = NCFilePermissionEditCell.self + row = XLFormRowDescriptor(tag: "kNMCFilePermissionEditCellHideDownload", rowType: "kNMCFilePermissionEditCell", title: NSLocalizedString("_PERMISSIONS_", comment: "")) + row.cellConfig["titleLabel.text"] = NSLocalizedString("_share_hide_download_", comment: "") + row.cellConfig["switchControl.onTintColor"] = NCBrandColor.shared.customer + row.cellClass = NCFilePermissionEditCell.self + row.height = 44 + section.addFormRow(row) + } + + //password + if isPasswordOptionsVisible() { + + // Set password + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFilePermissionEditCell"] = NCFilePermissionEditCell.self + row = XLFormRowDescriptor(tag: "kNMCFilePermissionEditPasswordCellWithText", rowType: "kNMCFilePermissionEditCell", title: NSLocalizedString("_PERMISSIONS_", comment: "")) + row.cellConfig["titleLabel.text"] = NSLocalizedString("_set_password_", comment: "") + row.cellConfig["switchControl.onTintColor"] = NCBrandColor.shared.customer + row.cellClass = NCFilePermissionEditCell.self + row.height = 44 + section.addFormRow(row) + + // enter password input field + XLFormViewController.cellClassesForRowDescriptorTypes()["NMCSetPasswordCustomInputField"] = PasswordInputField.self + row = XLFormRowDescriptor(tag: "SetPasswordInputField", rowType: "NMCSetPasswordCustomInputField", title: NSLocalizedString("_filename_", comment: "")) + row.cellClass = PasswordInputField.self + row.cellConfig["fileNameInputTextField.placeholder"] = NSLocalizedString("_password_", comment: "") + row.cellConfig["fileNameInputTextField.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["fileNameInputTextField.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["fileNameInputTextField.textColor"] = NCBrandColor.shared.label + row.cellConfig["backgroundColor"] = NCBrandColor.shared.secondarySystemGroupedBackground + row.height = 44 + let hasPassword = oldTableShare?.password != nil && !oldTableShare!.password.isEmpty + row.hidden = NSNumber.init(booleanLiteral: !hasPassword) + section.addFormRow(row) + } + + //expiration + + // expiry date switch + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFilePermissionEditCell"] = NCFilePermissionEditCell.self + row = XLFormRowDescriptor(tag: "kNMCFilePermissionEditCellExpiration", rowType: "kNMCFilePermissionEditCell", title: NSLocalizedString("_share_expiration_date_", comment: "")) + row.cellConfig["titleLabel.text"] = NSLocalizedString("_share_expiration_date_", comment: "") + row.cellConfig["switchControl.onTintColor"] = NCBrandColor.shared.customer + row.cellClass = NCFilePermissionEditCell.self + row.height = 44 + section.addFormRow(row) + + // set expiry date field + XLFormViewController.cellClassesForRowDescriptorTypes()["kNCShareTextInputCell"] = NCShareTextInputCell.self + row = XLFormRowDescriptor(tag: "NCShareTextInputCellExpiry", rowType: "kNCShareTextInputCell", title: "") + row.cellClass = NCShareTextInputCell.self + row.cellConfig["cellTextField.placeholder"] = NSLocalizedString("_share_expiration_date_placeholder_", comment: "") + if !isNewShare { + if let date = oldTableShare?.expirationDate { + row.cellConfig["cellTextField.text"] = DateFormatter.shareExpDate.string(from: date as Date) + } + } + row.cellConfig["cellTextField.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["cellTextField.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["cellTextField.textColor"] = NCBrandColor.shared.label + if let date = oldTableShare?.expirationDate { + row.cellConfig["cellTextField.text"] = DateFormatter.shareExpDate.string(from: date as Date) + } + row.height = 44 + let hasExpiry = oldTableShare?.expirationDate != nil + row.hidden = NSNumber.init(booleanLiteral: !hasExpiry) + section.addFormRow(row) + + if isDownloadLimitVisible() { + // DownloadLimit switch + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFilePermissionEditCell"] = NCFilePermissionEditCell.self + row = XLFormRowDescriptor(tag: "kNMCFilePermissionEditCellDownloadLimit", rowType: "kNMCFilePermissionEditCell", title: NSLocalizedString("_share_download_limit_", comment: "")) + row.cellConfig["titleLabel.text"] = NSLocalizedString("_share_download_limit_", comment: "") + row.cellConfig["switchControl.onTintColor"] = NCBrandColor.shared.customer + row.cellClass = NCFilePermissionEditCell.self + row.height = 44 + section.addFormRow(row) + + // set Download Limit field + XLFormViewController.cellClassesForRowDescriptorTypes()["kNCShareTextInputCell"] = NCShareTextInputCell.self + row = XLFormRowDescriptor(tag: "NCShareTextInputCellDownloadLimit", rowType: "kNCShareTextInputCell", title: "") + row.cellClass = NCShareTextInputCell.self + row.cellConfig["cellTextField.placeholder"] = NSLocalizedString("_share_download_limit_placeholder_", comment: "") + row.cellConfig["cellTextField.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["cellTextField.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["cellTextField.textColor"] = NCBrandColor.shared.label + row.height = 44 + let downloadLimitSet = downloadLimit?.limit != nil + row.hidden = NSNumber.init(booleanLiteral: !downloadLimitSet) + if let value = downloadLimit?.limit { + row.cellConfig["cellTextField.text"] = "\(value)" + } + section.addFormRow(row) + + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFilePermissionCell"] = NCFilePermissionCell.self + row = XLFormRowDescriptor(tag: "kNMCDownloadLimitCell", rowType: "kNMCFilePermissionCell", title: "") + row.cellClass = NCFilePermissionCell.self + row.height = 44 + if downloadLimit?.limit != nil { + row.cellConfig["titleLabel.text"] = NSLocalizedString("_share_remaining_download_", comment: "") + " \(downloadLimit?.count ?? 0)" + } + row.cellConfig["titleLabel.textColor"] = NCBrandColor.shared.systemGray + row.disabled = true + row.hidden = NSNumber.init(booleanLiteral: !downloadLimitSet) + section.addFormRow(row) + } + + form.addFormSection(section) + self.form = form + } + + func reloadForm() { + self.form.delegate = nil + self.tableView.reloadData() + self.form.delegate = self + } + + func updateDownloadLimitUI() { + if let value = downloadLimit?.limit { + if let downloadLimitSwitchField: XLFormRowDescriptor = self.form.formRow(withTag: "kNMCFilePermissionEditCellDownloadLimit") { + if let indexPath = self.form.indexPath(ofFormRow: downloadLimitSwitchField) { + let cell = tableView.cellForRow(at: indexPath) as? NCFilePermissionEditCell + cell?.switchControl.isOn = true + } + + if let downloadLimitInputField: XLFormRowDescriptor = self.form.formRow(withTag: "NCShareTextInputCellDownloadLimit") { + downloadLimitInputField.hidden = false + if let indexPath = self.form.indexPath(ofFormRow: downloadLimitInputField) { + let cell = tableView.cellForRow(at: indexPath) as? NCShareTextInputCell + cell?.cellTextField.text = "\(value)" + } + } + + if let downloadLimitInputField: XLFormRowDescriptor = self.form.formRow(withTag: "kNMCDownloadLimitCell") { + downloadLimitInputField.hidden = false + if let indexPath = self.form.indexPath(ofFormRow: downloadLimitInputField) { + let cell = tableView.cellForRow(at: indexPath) as? NCFilePermissionCell + cell?.titleLabel.text = NSLocalizedString("_share_remaining_download_", comment: "") + " \(downloadLimit?.count ?? 0)" + } + } + } + } + } + + func getDownloadLimitSwitchCell() -> NCFilePermissionEditCell? { + if let downloadLimitSwitchField: XLFormRowDescriptor = self.form.formRow(withTag: "kNMCFilePermissionEditCellDownloadLimit") { + if let indexPath = self.form.indexPath(ofFormRow: downloadLimitSwitchField) { + let cell = tableView.cellForRow(at: indexPath) as? NCFilePermissionEditCell + return cell + } + } + return nil + } + + func getDownloadLimitInputCell() -> NCShareTextInputCell? { + if let downloadLimitInputField: XLFormRowDescriptor = self.form.formRow(withTag: "NCShareTextInputCellDownloadLimit") { + if let indexPath = self.form.indexPath(ofFormRow: downloadLimitInputField) { + let cell = tableView.cellForRow(at: indexPath) as? NCShareTextInputCell + return cell + } + } + return nil + } + + // MARK: - Row Descriptor Value Changed + + override func didSelectFormRow(_ formRow: XLFormRowDescriptor!) { + guard let metadata = self.metadata else { return } + + switch formRow.tag { + case "NCFilePermissionCellRead": + + let value = CCUtility.getPermissionsValue(byCanEdit: false, andCanCreate: false, andCanChange: false, andCanDelete: false, andCanShare: canReshareTheShare(), andIsFolder: metadata.directory) + self.permission = value +// self.permissions = "RDNVCK" + metadata.permissions = "RDNVCK" + if let row : XLFormRowDescriptor = self.form.formRow(withTag: "NCFilePermissionCellRead") { + row.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: NCBrandColor.shared.customer, size: 25.0) + if let row1 : XLFormRowDescriptor = self.form.formRow(withTag: "kNMCFilePermissionCellEditing") { + row1.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: .clear, size: 25.0) + } + if let row2 : XLFormRowDescriptor = self.form.formRow(withTag: "NCFilePermissionCellFileDrop") { + row2.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: .clear, size: 25.0) + } + } + + self.reloadForm() + break + case "kNMCFilePermissionCellEditing": + let value = CCUtility.getPermissionsValue(byCanEdit: true, andCanCreate: true, andCanChange: true, andCanDelete: true, andCanShare: canReshareTheShare(), andIsFolder: metadata.directory) + self.permission = value +// self.permissions = "RGDNV" + metadata.permissions = "RGDNV" + if let row : XLFormRowDescriptor = self.form.formRow(withTag: "NCFilePermissionCellRead") { + row.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: .clear, size: 25.0) + } + if let row1 : XLFormRowDescriptor = self.form.formRow(withTag: "kNMCFilePermissionCellEditing") { + row1.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: NCBrandColor.shared.customer, size: 25.0) + } + if let row2 : XLFormRowDescriptor = self.form.formRow(withTag: "NCFilePermissionCellFileDrop") { + row2.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: .clear, size: 25.0) + } + self.reloadForm() + break + case "NCFilePermissionCellFileDrop": + self.permission = NCGlobal.shared.permissionCreateShare +// self.permissions = "RGDNVCK" + metadata.permissions = "RGDNVCK" + if let row : XLFormRowDescriptor = self.form.formRow(withTag: "NCFilePermissionCellRead") { + row.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: .clear, size: 25.0) + } + if let row1 : XLFormRowDescriptor = self.form.formRow(withTag: "kNMCFilePermissionCellEditing") { + row1.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: .clear, size: 25.0) + } + if let row2 : XLFormRowDescriptor = self.form.formRow(withTag: "NCFilePermissionCellFileDrop") { + row2.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: NCBrandColor.shared.customer, size: 25.0) + } + self.reloadForm() + break + default: + break + } } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { @@ -158,57 +591,293 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg guard let cellConfig = cellConfig as? NCAdvancedPermission else { cellConfig.didSelect(for: share) tableView.reloadData() + } + } + + func canReshareTheShare() -> Bool { + if let permissionValue = self.permission { + let canReshare = CCUtility.isPermission(toCanShare: permissionValue) + return canReshare + } else { + return false + } + } + + override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + if let advancePermissionHeaderRow: XLFormRowDescriptor = self.form.formRow(withTag: "NCFilePermissionCellAdvanceTxt") { + if let advancePermissionHeaderRowIndexPath = form.indexPath(ofFormRow: advancePermissionHeaderRow), indexPath == advancePermissionHeaderRowIndexPath { + let cell = cell as? NCFilePermissionCell + cell?.seperatorBelowFull.isHidden = isLinkShare() + } + } + + //can Reshare + if let canReshareRow: XLFormRowDescriptor = self.form.formRow(withTag: "kNMCFilePermissionEditCellEditingCanShare") { + if let canReShareRowIndexPath = form.indexPath(ofFormRow: canReshareRow), indexPath == canReShareRowIndexPath { + let cell = cell as? NCFilePermissionEditCell + // Can reshare (file) + if let permissionValue = self.permission { + let canReshare = CCUtility.isPermission(toCanShare: permissionValue) + cell?.switchControl.isOn = canReshare + } else { + //new share + cell?.switchControl.isOn = false + } + } + } + //hide download + if let hideDownloadRow: XLFormRowDescriptor = self.form.formRow(withTag: "kNMCFilePermissionEditCellHideDownload"){ + if let hideDownloadRowIndexPath = form.indexPath(ofFormRow: hideDownloadRow), indexPath == hideDownloadRowIndexPath { + let cell = cell as? NCFilePermissionEditCell + cell?.switchControl.isOn = oldTableShare?.hideDownload ?? false + cell?.titleLabel.isEnabled = !(self.permission == NCGlobal.shared.permissionCreateShare) + cell?.switchControl.isEnabled = !(self.permission == NCGlobal.shared.permissionCreateShare) + cell?.isUserInteractionEnabled = !(self.permission == NCGlobal.shared.permissionCreateShare) + } + + // set password + if let setPassword : XLFormRowDescriptor = self.form.formRow(withTag: "kNMCFilePermissionEditPasswordCellWithText") { + if let setPasswordIndexPath = self.form.indexPath(ofFormRow: setPassword), indexPath == setPasswordIndexPath { + let passwordCell = cell as? NCFilePermissionEditCell + if let password = oldTableShare?.password { + passwordCell?.switchControl.isOn = !password.isEmpty + } else { + passwordCell?.switchControl.isOn = false + } + } + } + } + + //updateExpiryDateSwitch + if let expiryRow : XLFormRowDescriptor = self.form.formRow(withTag: "kNMCFilePermissionEditCellExpiration") { + if let expiryIndexPath = self.form.indexPath(ofFormRow: expiryRow), indexPath == expiryIndexPath { + let cell = cell as? NCFilePermissionEditCell + if oldTableShare?.expirationDate != nil { + cell?.switchControl.isOn = true + } else { + //new share + cell?.switchControl.isOn = false + } + } + } + + //SetDownloadLimitSwitch + if let limitRow : XLFormRowDescriptor = self.form.formRow(withTag: "kNMCFilePermissionEditCellDownloadLimit") { + if let expiryIndexPath = self.form.indexPath(ofFormRow: limitRow), indexPath == expiryIndexPath { + let cell = cell as? NCFilePermissionEditCell + cell?.switchControl.isOn = downloadLimit?.limit != nil + } + } + + //SetDownloadLimitSwitch + if let downloadlimitFieldRow : XLFormRowDescriptor = self.form.formRow(withTag: "NCShareTextInputCellDownloadLimit") { + if let downloadlimitIndexPath = self.form.indexPath(ofFormRow: downloadlimitFieldRow), indexPath == downloadlimitIndexPath { + let cell = cell as? NCShareTextInputCell + cell?.cellTextField.text = "\(downloadLimit?.limit ?? 0)" + } + } + + //SetDownloadLimitSwitch + if downloadLimit?.count != nil { + if let downloadlimitFieldRow : XLFormRowDescriptor = self.form.formRow(withTag: "kNMCDownloadLimitCell") { + if let downloadlimitIndexPath = self.form.indexPath(ofFormRow: downloadlimitFieldRow), indexPath == downloadlimitIndexPath { + let cell = cell as? NCFilePermissionCell + cell?.titleLabel.text = NSLocalizedString("_share_remaining_download_", comment: "") + " \(downloadLimit?.count ?? 0)" + cell?.seperatorBelowFull.isHidden = true + cell?.seperatorBelow.isHidden = true + } + } + } + } + + override func formRowDescriptorValueHasChanged(_ formRow: XLFormRowDescriptor!, oldValue: Any!, newValue: Any!) { + super.formRowDescriptorValueHasChanged(formRow, oldValue: oldValue, newValue: newValue) + + switch formRow.tag { + + case "kNMCFilePermissionEditCellEditingCanShare": + if let value = newValue as? Bool { + canReshareValueChanged(isOn: value) + } + + case "kNMCFilePermissionEditCellHideDownload": + if let value = newValue as? Bool { + share.hideDownload = value + } + + case "kNMCFilePermissionEditPasswordCellWithText": + if let value = newValue as? Bool { + if let setPasswordInputField : XLFormRowDescriptor = self.form.formRow(withTag: "SetPasswordInputField") { + if let indexPath = self.form.indexPath(ofFormRow: setPasswordInputField) { + let cell = tableView.cellForRow(at: indexPath) as? PasswordInputField + cell?.fileNameInputTextField.text = "" + } + share.password = "" + setPasswordInputField.hidden = !value + } + } + + case "kNCShareTextInputCellCustomLinkField": + if let label = formRow.value as? String { + self.form.delegate = nil + share.label = label + self.form.delegate = self + } + + case "SetPasswordInputField": + if let pwd = formRow.value as? String { + self.form.delegate = nil + share.password = pwd + self.form.delegate = self + } + + case "kNMCFilePermissionEditCellLinkLabel": + if let label = formRow.value as? String { + self.form.delegate = nil + share.label = label + self.form.delegate = self + } + + case "kNMCFilePermissionEditCellExpiration": + if let value = newValue as? Bool { + if let inputField : XLFormRowDescriptor = self.form.formRow(withTag: "NCShareTextInputCellExpiry") { + inputField.hidden = !value + } + } + + case "kNMCFilePermissionEditCellDownloadLimit": + if let value = newValue as? Bool { + self.downloadLimit = DownloadLimit() + self.downloadLimit?.limit = value ? 0 : nil + if let inputField : XLFormRowDescriptor = self.form.formRow(withTag: "NCShareTextInputCellDownloadLimit") { + inputField.hidden = !value + if let indexPath = self.form.indexPath(ofFormRow: inputField) { + let cell = tableView.cellForRow(at: indexPath) as? NCShareTextInputCell + cell?.cellTextField.text = "" + } + } + + if let inputField : XLFormRowDescriptor = self.form.formRow(withTag: "kNMCDownloadLimitCell") { + inputField.hidden = !value + if let indexPath = self.form.indexPath(ofFormRow: inputField) { + let cell = tableView.cellForRow(at: indexPath) as? NCFilePermissionCell + cell?.seperatorBelowFull.isHidden = true + cell?.seperatorBelow.isHidden = true + cell?.titleLabel.text = "" + } + } + } + + case "NCShareTextInputCellExpiry": + if let exp = formRow.value as? Date { + self.form.delegate = nil + self.share.expirationDate = exp as NSDate + self.form.delegate = self + } + + default: + break + } + } + //Check file type is collabora + func checkIsCollaboraFile() -> Bool { + guard let metadata = metadata else { + return false + } + + // EDITORS + let editors = NCUtility().isDirectEditing(account: metadata.account, contentType: metadata.contentType) + let availableRichDocument = NCUtility().isRichDocument(metadata) + + // RichDocument: Collabora + return (availableRichDocument && editors.count == 0) + } + + func isFileDropOptionVisible() -> Bool { + return (metadata.directory && (isLinkShare() || isExternalUserShare())) + } + + func isLinkShare() -> Bool { + return NCShareCommon().isLinkShare(shareType: shareType) + } + + func isExternalUserShare() -> Bool { + return NCShareCommon().isExternalUserShare(shareType: shareType) + } + + func isInternalUser() -> Bool { + return NCShareCommon().isInternalUser(shareType: shareType) + } + + func isCanReshareOptionVisible() -> Bool { + return isInternalUser() + } + + func isHideDownloadOptionVisible() -> Bool { + return !isInternalUser() + } + + func isPasswordOptionsVisible() -> Bool { + return !isInternalUser() + } + + func isDownloadLimitVisible() -> Bool { + return isLinkShare() && !(metadata.directory) + } + + func canReshareValueChanged(isOn: Bool) { + + guard let oldTableShare = oldTableShare, let permission = self.permission else { + self.permission = isOn ? (self.permission ?? 0) + NCGlobal.shared.permissionShareShare : ((self.permission ?? 0) - NCGlobal.shared.permissionShareShare) return } - switch cellConfig { - case .limitDownload: - let storyboard = UIStoryboard(name: "NCShare", bundle: nil) - guard let viewController = storyboard.instantiateViewController(withIdentifier: "NCShareDownloadLimit") as? NCShareDownloadLimitViewController else { return } - viewController.downloadLimit = self.downloadLimit - viewController.metadata = self.metadata - viewController.share = self.share - viewController.shareDownloadLimitTableViewControllerDelegate = self - viewController.onDismiss = tableView.reloadData - self.navigationController?.pushViewController(viewController, animated: true) - case .hideDownload: - share.hideDownload.toggle() - tableView.reloadData() - case .expirationDate: - let cell = tableView.cellForRow(at: indexPath) as? NCShareDateCell - cell?.textField.becomeFirstResponder() - cell?.checkMaximumDate(account: metadata.account) - case .password: - guard share.password.isEmpty else { - share.password = "" - tableView.reloadData() - return + let canEdit = CCUtility.isAnyPermission(toEdit: permission) + let canCreate = CCUtility.isPermission(toCanCreate: permission) + let canChange = CCUtility.isPermission(toCanChange: permission) + let canDelete = CCUtility.isPermission(toCanDelete: permission) + + if metadata.directory { + self.permission = CCUtility.getPermissionsValue(byCanEdit: canEdit, andCanCreate: canCreate, andCanChange: canChange, andCanDelete: canDelete, andCanShare: isOn, andIsFolder: metadata.directory) + } else { + if isOn { + if canEdit { + self.permission = CCUtility.getPermissionsValue(byCanEdit: true, andCanCreate: true, andCanChange: true, andCanDelete: true, andCanShare: isOn, andIsFolder: metadata.directory) + } else { + self.permission = CCUtility.getPermissionsValue(byCanEdit: false, andCanCreate: false, andCanChange: false, andCanDelete: false, andCanShare: isOn, andIsFolder: metadata.directory) + } + } else { + if canEdit { + self.permission = CCUtility.getPermissionsValue(byCanEdit: true, andCanCreate: true, andCanChange: true, andCanDelete: true, andCanShare: isOn, andIsFolder: metadata.directory) + } else { + self.permission = CCUtility.getPermissionsValue(byCanEdit: false, andCanCreate: false, andCanChange: false, andCanDelete: false, andCanShare: isOn, andIsFolder: metadata.directory) + } } let alertController = UIAlertController.password(titleKey: "_share_password_") { password in self.share.password = password ?? "" tableView.reloadData() } self.present(alertController, animated: true) - case .note: - let storyboard = UIStoryboard(name: "NCShare", bundle: nil) - guard let viewNewUserComment = storyboard.instantiateViewController(withIdentifier: "NCShareNewUserAddComment") as? NCShareNewUserAddComment else { return } - viewNewUserComment.metadata = self.metadata - viewNewUserComment.share = self.share - viewNewUserComment.onDismiss = tableView.reloadData - self.navigationController?.pushViewController(viewNewUserComment, animated: true) - case .label: - let alertController = UIAlertController.withTextField(titleKey: "_share_link_name_") { textField in - textField.placeholder = cellConfig.title - textField.text = self.share.label - } completion: { newValue in - self.share.label = newValue ?? "" - self.setNavigationTitle() - tableView.reloadData() - } - self.present(alertController, animated: true) - case .downloadAndSync: - share.downloadAndSync.toggle() - tableView.reloadData() +// case .note: +// let storyboard = UIStoryboard(name: "NCShare", bundle: nil) +// guard let viewNewUserComment = storyboard.instantiateViewController(withIdentifier: "NCShareNewUserAddComment") as? NCShareNewUserAddComment else { return } +// viewNewUserComment.metadata = self.metadata +// viewNewUserComment.share = self.share +// viewNewUserComment.onDismiss = tableView.reloadData +// self.navigationController?.pushViewController(viewNewUserComment, animated: true) +// case .label: +// let alertController = UIAlertController.withTextField(titleKey: "_share_link_name_") { textField in +// textField.placeholder = cellConfig.title +// textField.text = self.share.label +// } completion: { newValue in +// self.share.label = newValue ?? "" +// self.setNavigationTitle() +// tableView.reloadData() +// } +// self.present(alertController, animated: true) +// case .downloadAndSync: +// share.downloadAndSync.toggle() +// tableView.reloadData() } } @@ -266,6 +935,31 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg navigationController?.popViewController(animated: true) } + + func getDownloadLimit() { + NCActivityIndicator.shared.start(backgroundView: view) + NMCCommunication.shared.getDownloadLimit(token: oldTableShare?.token ?? "") { [weak self] (downloadLimit: DownloadLimit?, error: String) in + DispatchQueue.main.async { + NCActivityIndicator.shared.stop() + if let downloadLimit = downloadLimit { + self?.downloadLimit = downloadLimit + } + self?.updateDownloadLimitUI() + } + } + } + + func setDownloadLimit(deleteLimit: Bool, limit: String) { + NMCCommunication.shared.setDownloadLimit(deleteLimit: deleteLimit, limit: limit, token: oldTableShare?.token ?? "") { (success, errorMessage) in + print(success) + } + } + + func showDownloadLimitError(message: String) { + let alertController = UIAlertController(title: "", message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { action in })) + self.present(alertController, animated: true) + } } // MARK: - NCShareDownloadLimitTableViewControllerDelegate diff --git a/iOSClient/Share/Advanced/NCShareAdvancePermissionFooter.swift b/iOSClient/Share/Advanced/NCShareAdvancePermissionFooter.swift index dfee243827..455b5da0c6 100644 --- a/iOSClient/Share/Advanced/NCShareAdvancePermissionFooter.swift +++ b/iOSClient/Share/Advanced/NCShareAdvancePermissionFooter.swift @@ -35,10 +35,11 @@ class NCShareAdvancePermissionFooter: UIView { func setupUI(delegate: NCShareAdvanceFotterDelegate?, account: String) { self.delegate = delegate - backgroundColor = .clear - + buttonCancel.addTarget(self, action: #selector(cancelClicked), for: .touchUpInside) + buttonNext.addTarget(self, action: #selector(nextClicked), for: .touchUpInside) buttonCancel.setTitle(NSLocalizedString("_cancel_", comment: ""), for: .normal) - buttonCancel.layer.cornerRadius = 25 + buttonNext.setTitle(NSLocalizedString(delegate?.isNewShare == true ? "_next_" : "_apply_changes_", comment: ""), for: .normal) + buttonCancel.layer.cornerRadius = 10 buttonCancel.layer.masksToBounds = true buttonCancel.layer.borderWidth = 1 buttonCancel.layer.borderColor = NCBrandColor.shared.textColor2.cgColor @@ -51,7 +52,18 @@ class NCShareAdvancePermissionFooter: UIView { buttonNext.layer.masksToBounds = true buttonNext.backgroundColor = NCBrandColor.shared.getElement(account: account) buttonNext.addTarget(self, action: #selector(nextClicked(_:)), for: .touchUpInside) + + addShadow(location: .top) + layer.cornerRadius = 10 + layer.masksToBounds = true + backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + buttonCancel.setTitleColor(NCBrandColor.shared.label, for: .normal) + buttonCancel.layer.borderColor = NCBrandColor.shared.label.cgColor + buttonCancel.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + buttonNext.setBackgroundColor(NCBrandColor.shared.customer, for: .normal) buttonNext.setTitleColor(.white, for: .normal) + buttonNext.layer.cornerRadius = 10 + buttonNext.layer.masksToBounds = true } @objc func cancelClicked(_ sender: Any?) { diff --git a/iOSClient/Share/Advanced/NCShareAdvancePermissionFooter.xib b/iOSClient/Share/Advanced/NCShareAdvancePermissionFooter.xib index b4ec72ed06..36b09589ef 100644 --- a/iOSClient/Share/Advanced/NCShareAdvancePermissionFooter.xib +++ b/iOSClient/Share/Advanced/NCShareAdvancePermissionFooter.xib @@ -1,67 +1,60 @@ - + - + - - - + + - + + - + - - - - - - - + - + - - - - - - - + + + - - + + - + - - - - - - - - diff --git a/iOSClient/Share/Advanced/NCShareAdvancePermissionHeader.xib b/iOSClient/Share/Advanced/NCShareAdvancePermissionHeader.xib new file mode 100644 index 0000000000..161dc96732 --- /dev/null +++ b/iOSClient/Share/Advanced/NCShareAdvancePermissionHeader.xib @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Share/Advanced/NCShareHeaderCustomCell.swift b/iOSClient/Share/Advanced/NCShareHeaderCustomCell.swift new file mode 100644 index 0000000000..cb5dae8c1f --- /dev/null +++ b/iOSClient/Share/Advanced/NCShareHeaderCustomCell.swift @@ -0,0 +1,30 @@ +// +// NCShareHeaderCustomCell.swift +// Nextcloud +// +// Created by T-systems on 11/08/21. +// Copyright © 2021 Marino Faggiana. All rights reserved. +// + +import UIKit + +class NCShareHeaderCustomCell: XLFormBaseCell { + + @IBOutlet weak var titleLabel: UILabel! + + override func awakeFromNib() { + super.awakeFromNib() + self.selectionStyle = .none + self.backgroundColor = .clear + } + + override func configure() { + super.configure() + } + + override func update() { + self.backgroundColor = .clear + self.titleLabel.textColor = NCBrandColor.shared.systemGray + super.update() + } +} diff --git a/iOSClient/Share/Advanced/NCShareHeaderCustomCell.xib b/iOSClient/Share/Advanced/NCShareHeaderCustomCell.xib new file mode 100644 index 0000000000..89ba3781e5 --- /dev/null +++ b/iOSClient/Share/Advanced/NCShareHeaderCustomCell.xib @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Share/Advanced/NCShareNewUserAddComment.swift b/iOSClient/Share/Advanced/NCShareNewUserAddComment.swift index 0c6bb586ef..a7fe311e0c 100644 --- a/iOSClient/Share/Advanced/NCShareNewUserAddComment.swift +++ b/iOSClient/Share/Advanced/NCShareNewUserAddComment.swift @@ -1,9 +1,12 @@ // +// // NCShareNewUserAddComment.swift // Nextcloud // // Created by TSI-mc on 21/06/21. -// Copyright © 2022 All rights reserved. +// Copyright © 2022 Henrik Storch. All rights reserved. +// +// Author Henrik Storch // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -25,39 +28,60 @@ import NextcloudKit class NCShareNewUserAddComment: UIViewController, NCShareNavigationTitleSetting { @IBOutlet weak var headerContainerView: UIView! - @IBOutlet weak var sharingLabel: UILabel! - @IBOutlet weak var noteTextField: UITextView! - + @IBOutlet weak var labelSharing: UILabel! + @IBOutlet weak var labelNote: UILabel! + @IBOutlet weak var commentTextView: UITextView! + @IBOutlet weak var btnCancel: UIButton! + @IBOutlet weak var btnSendShare: UIButton! + @IBOutlet weak var buttonContainerView: UIView! let contentInsets: CGFloat = 16 - var onDismiss: (() -> Void)? - - public var share: Shareable! + public var share: NCTableShareable! public var metadata: tableMetadata! + var isNewShare: Bool { share is NCTableShareOptions } + var networking: NCShareNetworking? override func viewDidLoad() { super.viewDidLoad() self.setNavigationTitle() - - NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) - - sharingLabel.text = NSLocalizedString("_share_note_recipient_", comment: "") - - noteTextField.textContainerInset = UIEdgeInsets(top: contentInsets, left: contentInsets, bottom: contentInsets, right: contentInsets) - noteTextField.text = share.note - let toolbar = UIToolbar.toolbar { - self.noteTextField.resignFirstResponder() - self.noteTextField.text = "" - self.share.note = "" - } onDone: { - self.noteTextField.resignFirstResponder() - self.share.note = self.noteTextField.text - self.navigationController?.popViewController(animated: true) + changeTheming() + setupHeader() + } + + @objc func changeTheming() { + self.view.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + self.commentTextView.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + commentTextView.textColor = NCBrandColor.shared.label + btnCancel.setTitleColor(NCBrandColor.shared.label, for: .normal) + btnCancel.layer.borderColor = NCBrandColor.shared.label.cgColor + btnCancel.backgroundColor = .clear + buttonContainerView.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + btnSendShare.setBackgroundColor(NCBrandColor.shared.customer, for: .normal) + btnSendShare.setTitleColor(.white, for: .normal) + commentTextView.layer.borderColor = NCBrandColor.shared.label.cgColor + commentTextView.layer.borderWidth = 1 + commentTextView.layer.cornerRadius = 4.0 + commentTextView.showsVerticalScrollIndicator = false + commentTextView.textContainerInset = UIEdgeInsets(top: contentInsets, left: contentInsets, bottom: contentInsets, right: contentInsets) + btnCancel.setTitle(NSLocalizedString("_cancel_", comment: ""), for: .normal) + btnCancel.layer.cornerRadius = 10 + btnCancel.layer.masksToBounds = true + btnCancel.layer.borderWidth = 1 + btnSendShare.setTitle(NSLocalizedString("_send_share_", comment: ""), for: .normal) + btnSendShare.layer.cornerRadius = 10 + btnSendShare.layer.masksToBounds = true + labelSharing.text = NSLocalizedString("_sharing_", comment: "") + labelNote.text = NSLocalizedString("_share_note_recipient_", comment: "") + commentTextView.inputAccessoryView = UIToolbar.doneToolbar { [weak self] in + self?.commentTextView.resignFirstResponder() } - - noteTextField.inputAccessoryView = toolbar.wrappedSafeAreaContainer - - guard let headerView = (Bundle.main.loadNibNamed("NCShareHeader", owner: self, options: nil)?.first as? NCShareHeader) else { return } + } + + func setupHeader(){ + guard let headerView = (Bundle.main.loadNibNamed("NCShareAdvancePermissionHeader", owner: self, options: nil)?.first as? NCShareAdvancePermissionHeader) else { return } + headerView.ocId = metadata.ocId headerContainerView.addSubview(headerView) headerView.frame = headerContainerView.frame headerView.translatesAutoresizingMaskIntoConstraints = false @@ -65,29 +89,48 @@ class NCShareNewUserAddComment: UIViewController, NCShareNavigationTitleSetting headerView.bottomAnchor.constraint(equalTo: headerContainerView.bottomAnchor).isActive = true headerView.leftAnchor.constraint(equalTo: headerContainerView.leftAnchor).isActive = true headerView.rightAnchor.constraint(equalTo: headerContainerView.rightAnchor).isActive = true - headerView.setupUI(with: metadata) } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - share.note = noteTextField.text - onDismiss?() + @IBAction func cancelClicked(_ sender: Any) { + self.navigationController?.popToRootViewController(animated: true) + } + + @IBAction func sendShareClicked(_ sender: Any) { + share.note = commentTextView.text + if isNewShare { + networking?.createShare(option: share) + } else { + networking?.updateShare(option: share) + } + self.navigationController?.popToRootViewController(animated: true) } @objc func adjustForKeyboard(notification: Notification) { guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue, - let globalTextViewFrame = noteTextField.superview?.convert(noteTextField.frame, to: nil) else { return } - + let globalTextViewFrame = commentTextView.superview?.convert(commentTextView.frame, to: nil) else { return } let keyboardScreenEndFrame = keyboardValue.cgRectValue let portionCovoredByLeyboard = globalTextViewFrame.maxY - keyboardScreenEndFrame.minY if notification.name == UIResponder.keyboardWillHideNotification || portionCovoredByLeyboard < 0 { - noteTextField.contentInset = .zero + commentTextView.contentInset = .zero } else { - noteTextField.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: portionCovoredByLeyboard, right: 0) + commentTextView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: portionCovoredByLeyboard, right: 0) } + commentTextView.scrollIndicatorInsets = commentTextView.contentInset + } - noteTextField.scrollIndicatorInsets = noteTextField.contentInset + @objc func keyboardWillShow(notification: Notification) { + if UIScreen.main.bounds.width <= 375 { + if view.frame.origin.y == 0 { + self.view.frame.origin.y -= 200 + } + } + } + + @objc func keyboardWillHide(notification: Notification) { + if view.frame.origin.y != 0 { + self.view.frame.origin.y = 0 + } } } diff --git a/iOSClient/Share/Advanced/NCShareTextInputCell.swift b/iOSClient/Share/Advanced/NCShareTextInputCell.swift new file mode 100644 index 0000000000..26da27ef03 --- /dev/null +++ b/iOSClient/Share/Advanced/NCShareTextInputCell.swift @@ -0,0 +1,151 @@ +// +// NCShareTextInputCell.swift +// Nextcloud +// +// Created by T-systems on 20/09/21. +// Copyright © 2021 Kunal. All rights reserved. +// + +import UIKit + +class NCShareTextInputCell: XLFormBaseCell, UITextFieldDelegate { + + @IBOutlet weak var seperator: UIView! + @IBOutlet weak var seperatorBottom: UIView! + @IBOutlet weak var cellTextField: UITextField! + @IBOutlet weak var calendarImageView: UIImageView! + + let datePicker = UIDatePicker() + var expirationDateText: String! + var expirationDate: NSDate! + + override func awakeFromNib() { + super.awakeFromNib() + self.cellTextField.delegate = self + self.cellTextField.isEnabled = true + calendarImageView.image = UIImage(named: "calender")?.imageColor(NCBrandColor.shared.brandElement) + self.selectionStyle = .none + self.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + self.cellTextField.attributedPlaceholder = NSAttributedString(string: "", + attributes: [NSAttributedString.Key.foregroundColor: NCBrandColor.shared.fileFolderName]) + self.cellTextField.textColor = NCBrandColor.shared.singleTitleColorButton + } + + override func configure() { + super.configure() + } + + override func update() { + super.update() + calendarImageView.isHidden = rowDescriptor.tag != "NCShareTextInputCellExpiry" + if rowDescriptor.tag == "NCShareTextInputCellExpiry" { + seperator.isHidden = true + setDatePicker(sender: self.cellTextField) + } else if rowDescriptor.tag == "NCShareTextInputCellDownloadLimit" { + seperator.isHidden = true + cellTextField.keyboardType = .numberPad + setDoneButton(sender: self.cellTextField) + } + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + if self.cellTextField == textField { + if let rowDescriptor = rowDescriptor, let text = self.cellTextField.text { + + if (text + " ").isEmpty == false { + rowDescriptor.value = self.cellTextField.text! + string + } else { + rowDescriptor.value = nil + } + } + } + + self.formViewController().textField(textField, shouldChangeCharactersIn: range, replacementString: string) + return true + } + + func textFieldDidEndEditing(_ textField: UITextField) { + rowDescriptor.value = cellTextField.text + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + self.formViewController()?.textFieldShouldReturn(textField) + return true + } + + func textFieldShouldClear(_ textField: UITextField) -> Bool { + self.formViewController()?.textFieldShouldClear(textField) + return true + } + + override class func formDescriptorCellHeight(for rowDescriptor: XLFormRowDescriptor!) -> CGFloat { + return 30 + } + + override func formDescriptorCellDidSelected(withForm controller: XLFormViewController!) { + self.selectionStyle = .none + } + + func setDatePicker(sender: UITextField) { + //Format Date + datePicker.datePickerMode = .date + datePicker.minimumDate = Date.tomorrow + if #available(iOS 13.4, *) { + datePicker.preferredDatePickerStyle = .wheels + datePicker.sizeToFit() + } + //ToolBar + let toolbar = UIToolbar(); + toolbar.sizeToFit() + let doneButton = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: .plain, target: self, action: #selector(doneDatePicker)); + let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil) + let cancelButton = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: .plain, target: self, action: #selector(cancelDatePicker)); + + toolbar.setItems([cancelButton, spaceButton, doneButton], animated: false) + + sender.inputAccessoryView = toolbar + sender.inputView = datePicker + } + + @objc func doneDatePicker() { + let dateFormatter = DateFormatter() + dateFormatter.formatterBehavior = .behavior10_4 + dateFormatter.dateStyle = .medium + dateFormatter.dateFormat = NCShareAdvancePermission.displayDateFormat + + var expiryDate = dateFormatter.string(from: datePicker.date) + expiryDate = expiryDate.replacingOccurrences(of: "..", with: ".") + self.expirationDateText = expiryDate + + self.expirationDate = datePicker.date as NSDate + self.cellTextField.text = self.expirationDateText + self.rowDescriptor.value = self.expirationDate + self.cellTextField.endEditing(true) + } + + @objc func cancelDatePicker() { + self.cellTextField.endEditing(true) + } + + func setDoneButton(sender: UITextField) { + //ToolBar + let toolbar = UIToolbar(); + toolbar.sizeToFit() + let doneButton = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: .plain, target: self, action: #selector(cancelDatePicker)); + let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil) + toolbar.setItems([spaceButton, doneButton], animated: false) + sender.inputAccessoryView = toolbar + } +} + +class NCSeparatorCell: XLFormBaseCell { + override func awakeFromNib() { + super.awakeFromNib() + selectionStyle = .none + } + + override func update() { + super.update() + self.selectionStyle = .none + } +} diff --git a/iOSClient/Share/Advanced/NCShareTextInputCell.xib b/iOSClient/Share/Advanced/NCShareTextInputCell.xib new file mode 100644 index 0000000000..eaa2f0cd13 --- /dev/null +++ b/iOSClient/Share/Advanced/NCShareTextInputCell.xib @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Share/NCSearchUserDropDownCell.xib b/iOSClient/Share/NCSearchUserDropDownCell.xib index 2ba38c8fa6..6c2ffde821 100755 --- a/iOSClient/Share/NCSearchUserDropDownCell.xib +++ b/iOSClient/Share/NCSearchUserDropDownCell.xib @@ -1,9 +1,9 @@ - + - + @@ -29,12 +29,12 @@ - + @@ -58,12 +58,12 @@ + - diff --git a/iOSClient/Share/NCShare+Helper.swift b/iOSClient/Share/NCShare+Helper.swift new file mode 100644 index 0000000000..b08bfa9358 --- /dev/null +++ b/iOSClient/Share/NCShare+Helper.swift @@ -0,0 +1,113 @@ +// +// NCShare+Helper.swift +// Nextcloud +// +// Created by Henrik Storch on 19.03.22. +// Copyright © 2022 Henrik Storch. All rights reserved. +// +// Author Henrik Storch +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +import UIKit +import NextcloudKit + +extension tableShare: NCTableShareable { } +extension NKShare: NCTableShareable { } + +protocol NCTableShareable: AnyObject { + var shareType: Int { get set } + var permissions: Int { get set } + var idShare: Int { get set } + var shareWith: String { get set } + var hideDownload: Bool { get set } + var password: String { get set } + var label: String { get set } + var note: String { get set } + var expirationDate: NSDate? { get set } + var shareWithDisplayname: String { get set } + var attributes: String? { get set } +} + +extension NCTableShareable { + var expDateString: String? { + guard let date = expirationDate else { return nil } + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss" + return dateFormatter.string(from: date as Date) + } + + func hasChanges(comparedTo other: NCTableShareable) -> Bool { + return other.shareType != shareType + || other.permissions != permissions + || other.hideDownload != hideDownload + || other.password != password + || other.label != label + || other.note != note + || other.expirationDate != expirationDate + } +} + +class NCTableShareOptions: NCTableShareable { + + var shareType: Int + var permissions: Int + + var idShare: Int = 0 + var shareWith: String = "" + + var hideDownload: Bool = false + var password: String = "" + var label: String = "" + var note: String = "" + var expirationDate: NSDate? + var shareWithDisplayname: String = "" + + var attributes: String? + + private init(shareType: Int, metadata: tableMetadata, password: String?) { + if metadata.e2eEncrypted, NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV12 { + self.permissions = NCPermissions().permissionCreateShare + } else { + self.permissions = NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityFileSharingDefaultPermission & metadata.sharePermissionsCollaborationServices + } + self.shareType = shareType + if let password = password { + self.password = password + } + } + + convenience init(sharee: NKSharee, metadata: tableMetadata, password: String?) { + self.init(shareType: sharee.shareType, metadata: metadata, password: password) + self.shareWith = sharee.shareWith + } + + static func shareLink(metadata: tableMetadata, password: String?) -> NCTableShareOptions { + return NCTableShareOptions(shareType: NCShareCommon().SHARE_TYPE_LINK, metadata: metadata, password: password) + } +} + +protocol NCShareDetail { + var share: NCTableShareable! { get } +} + +extension NCShareDetail where Self: UIViewController { + func setNavigationTitle() { + title = NSLocalizedString("_sharing_", comment: "") + if share.shareType != NCShareCommon().SHARE_TYPE_LINK { + title! = share.shareWithDisplayname.isEmpty ? share.shareWith : share.shareWithDisplayname + } + } +} diff --git a/iOSClient/Share/NCShare+NCCellDelegate.swift b/iOSClient/Share/NCShare+NCCellDelegate.swift index 06eeda91e7..f408665b57 100644 --- a/iOSClient/Share/NCShare+NCCellDelegate.swift +++ b/iOSClient/Share/NCShare+NCCellDelegate.swift @@ -48,7 +48,8 @@ extension NCShare: NCShareLinkCellDelegate, NCShareUserCellDelegate { func tapMenu(with tableShare: tableShare?, sender: Any) { if let tableShare = tableShare { - self.toggleShareMenu(for: tableShare, sender: sender) +// self.toggleShareMenu(for: tableShare, sender: sender) + self.toggleShareMenu(for: tableShare, sendMail: (tableShare.shareType != NCShareCommon().SHARE_TYPE_LINK), folder: metadata?.directory ?? false, sender: sender) } else { self.makeNewLinkShare() } @@ -62,5 +63,8 @@ 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) +// guard let tableShare = tableShare, +// let metadata = metadata else { return } +// self.toggleUserPermissionMenu(isDirectory: metadata.directory, tableShare: tableShare) } } diff --git a/iOSClient/Share/NCShare.storyboard b/iOSClient/Share/NCShare.storyboard index 5bf01c2fb8..85c52dc81f 100644 --- a/iOSClient/Share/NCShare.storyboard +++ b/iOSClient/Share/NCShare.storyboard @@ -10,289 +10,233 @@ - - - - - - - - - - - - - - - - - + - + - + - + - + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - + - + - + - - + + - - + + - - + + - - - + + + - + - - + + - - - + + + + - + - - - - + - - - - - - - - - - - + + + + + + + + + + + - - - - - + + + + + - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - + + + + + + + + - + - - - + + + + + + + - + - - + + - - + + - - + + - - + + - - - - - - - - - - + - + + + + + + + - - + + - - - - + + + + + - + - + - + - - + + @@ -371,44 +315,22 @@ - - - - - - - - - - - - - - - - - - + + + + + + - + - - - - - - - - - - diff --git a/iOSClient/Share/NCShare.swift b/iOSClient/Share/NCShare.swift index 2772838e99..ef14b0be71 100644 --- a/iOSClient/Share/NCShare.swift +++ b/iOSClient/Share/NCShare.swift @@ -30,17 +30,11 @@ import NextcloudKit import MarqueeLabel import ContactsUI -class NCShare: UIViewController, NCSharePagingContent { - @IBOutlet weak var viewContainerConstraint: NSLayoutConstraint! - @IBOutlet weak var sharedWithYouByView: UIView! - @IBOutlet weak var sharedWithYouByImage: UIImageView! - @IBOutlet weak var sharedWithYouByLabel: UILabel! - @IBOutlet weak var searchFieldTopConstraint: NSLayoutConstraint! - @IBOutlet weak var searchField: UISearchBar! - var textField: UIView? { searchField } +class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent { + + var textField: UITextField? { self.view.viewWithTag(Tag.searchField) as? UITextField } @IBOutlet weak var tableView: UITableView! - @IBOutlet weak var btnContact: UIButton! weak var appDelegate = UIApplication.shared.delegate as? AppDelegate @@ -54,6 +48,7 @@ class NCShare: UIViewController, NCSharePagingContent { var shareLinksCount = 0 var canReshare: Bool { + guard let metadata = metadata else { return true } return ((metadata.sharePermissionsCollaborationServices & NCPermissions().permissionShareShare) != 0) } @@ -75,22 +70,16 @@ class NCShare: UIViewController, NCSharePagingContent { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .systemBackground - - viewContainerConstraint.constant = height - searchFieldTopConstraint.constant = 0 - - searchField.placeholder = NSLocalizedString("_shareLinksearch_placeholder_", comment: "") - searchField.autocorrectionType = .no + view.backgroundColor = .secondarySystemGroupedBackground tableView.dataSource = self tableView.delegate = self tableView.allowsSelection = false - tableView.backgroundColor = .systemBackground - tableView.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 10, right: 0) + tableView.backgroundColor = .secondarySystemGroupedBackground tableView.register(UINib(nibName: "NCShareLinkCell", bundle: nil), forCellReuseIdentifier: "cellLink") tableView.register(UINib(nibName: "NCShareUserCell", bundle: nil), forCellReuseIdentifier: "cellUser") + tableView.register(UINib(nibName: "NCShareEmailFieldCell", bundle: nil), forCellReuseIdentifier: "NCShareEmailFieldCell") NotificationCenter.default.addObserver(self, selector: #selector(reloadData), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterReloadDataNCShare), object: nil) @@ -106,16 +95,36 @@ class NCShare: UIViewController, NCSharePagingContent { checkSharedWithYou() } + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) + guard let metadata = metadata else { return } + reloadData() networking = NCShareNetworking(metadata: metadata, view: self.view, delegate: self, session: session) let isVisible = (self.navigationController?.topViewController as? NCSharePaging)?.page == .sharing networking?.readShare(showLoadingIndicator: isVisible) - searchField.searchTextField.font = .systemFont(ofSize: 14) - searchField.delegate = self +// searchField.searchTextField.font = .systemFont(ofSize: 14) +// searchField.delegate = self +// if sharingEnabled { +// let isVisible = (self.navigationController?.topViewController as? NCSharePaging)?.page == .sharing +// networking?.readShare(showLoadingIndicator: isVisible) +// } + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: .done, target: self, action: #selector(exitTapped)) + navigationItem.largeTitleDisplayMode = .never + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + tableView.reloadData() } + @objc func exitTapped() { + self.dismiss(animated: true, completion: nil) + } + func makeNewLinkShare() { guard let advancePermission = UIStoryboard(name: "NCShare", bundle: nil).instantiateViewController(withIdentifier: "NCShareAdvancePermission") as? NCShareAdvancePermission, @@ -181,7 +190,48 @@ class NCShare: UIViewController, NCSharePagingContent { @objc func openShareProfile(_ sender: UITapGestureRecognizer) { self.showProfileMenu(userId: metadata.ownerId, session: session, sender: sender.view) } + + @objc func keyboardWillShow(notification: Notification) { + if UIDevice.current.userInterfaceIdiom == .phone { + if (UIScreen.main.bounds.width < 374 || UIDevice.current.orientation.isLandscape) { + if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { + if view.frame.origin.y == 0 { + self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + self.view.frame.origin.y -= keyboardSize.height + } + } + } else if UIScreen.main.bounds.height < 850 { + if view.frame.origin.y == 0 { + self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + self.view.frame.origin.y -= 70 + } + } else { + if view.frame.origin.y == 0 { + self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + self.view.frame.origin.y -= 40 + } + } + } + + if UIDevice.current.userInterfaceIdiom == .pad, UIDevice.current.orientation.isLandscape { + if view.frame.origin.y == 0 { + self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + self.view.frame.origin.y -= 230 + } + } + textField?.layer.borderColor = NCBrandColor.shared.brand.cgColor + } + + @objc func keyboardWillHide(notification: Notification) { + if view.frame.origin.y != 0 { + self.view.frame.origin.y = 0 + } + textField?.layer.borderColor = NCBrandColor.shared.label.cgColor + } + @objc func appWillEnterForeground(notification: Notification) { + reloadData() + } // MARK: - @objc func reloadData() { @@ -193,18 +243,64 @@ class NCShare: UIViewController, NCSharePagingContent { // MARK: - IBAction @IBAction func searchFieldDidEndOnExit(textField: UITextField) { - // https://stackoverflow.com/questions/25471114/how-to-validate-an-e-mail-address-in-swift - func isValidEmail(_ email: String) -> Bool { - - let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" - let emailPred = NSPredicate(format: "SELF MATCHES %@", emailRegEx) - return emailPred.evaluate(with: email) - } guard let searchString = textField.text, !searchString.isEmpty else { return } - if searchString.contains("@"), !isValidEmail(searchString) { return } + if searchString.contains("@"), !utility.isValidEmail(searchString) { return } networking?.getSharees(searchString: searchString) } - + + @IBAction func searchFieldDidChange(textField: UITextField) { + guard let searchString = textField.text else {return} + if searchString.count == 0 { + dropDown.hide() + } else { + networking?.getSharees(searchString: searchString) + } + } + + @IBAction func createLinkClicked(_ sender: Any) { + appDelegate?.adjust.trackEvent(TriggerEvent(CreateLink.rawValue)) + TealiumHelper.shared.trackEvent(title: "magentacloud-app.sharing.create", data: ["": ""]) + self.touchUpInsideButtonMenu(sender) + } + + @IBAction func touchUpInsideButtonMenu(_ sender: Any) { + + guard let metadata = metadata else { return } + let isFilesSharingPublicPasswordEnforced = NCGlobal.shared.capabilityFileSharingPubPasswdEnforced + let shares = NCManageDatabase.shared.getTableShares(metadata: metadata) + + if isFilesSharingPublicPasswordEnforced && shares.firstShareLink == nil { + let alertController = UIAlertController(title: NSLocalizedString("_enforce_password_protection_", comment: ""), message: "", preferredStyle: .alert) + alertController.addTextField { (textField) in + textField.isSecureTextEntry = true + } + alertController.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .default) { (action:UIAlertAction) in }) + let okAction = UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default) {[weak self] (action:UIAlertAction) in + let password = alertController.textFields?.first?.text + self?.networking?.createShareLink(password: password ?? "") + } + + alertController.addAction(okAction) + + present(alertController, animated: true, completion:nil) + } else if shares.firstShareLink == nil { + networking?.createShareLink(password: "") + } else { + networking?.createShareLink(password: "") + } + + } + + @IBAction func selectContactClicked(_ sender: Any) { + let cnPicker = CNContactPickerViewController() + cnPicker.delegate = self + cnPicker.displayedPropertyKeys = [CNContactEmailAddressesKey] + cnPicker.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0") + cnPicker.predicateForSelectionOfProperty = NSPredicate(format: "emailAddresses.@count > 0") + + self.present(cnPicker, animated: true) + } + func checkEnforcedPassword(shareType: Int, completion: @escaping (String?) -> Void) { guard capabilities.fileSharingPubPasswdEnforced, shareType == shareCommon.SHARE_TYPE_LINK || shareType == shareCommon.SHARE_TYPE_EMAIL @@ -223,7 +319,7 @@ class NCShare: UIViewController, NCSharePagingContent { } } -// MARK: - NCShareNetworkingDelegate + // MARK: - NCShareNetworkingDelegate extension NCShare: NCShareNetworkingDelegate { func readShareCompleted() { @@ -237,7 +333,10 @@ extension NCShare: NCShareNetworkingDelegate { } func unShareCompleted() { - reloadData() +// reloadData() +// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) + self.reloadData() } func updateShareWithError(idShare: Int) { @@ -250,18 +349,14 @@ extension NCShare: NCShareNetworkingDelegate { dropDown = DropDown() let appearance = DropDown.appearance() - // Setting up the blur effect - let blurEffect = UIBlurEffect(style: .light) // You can choose .dark, .extraLight, or .light - let blurEffectView = UIVisualEffectView(effect: blurEffect) - blurEffectView.frame = CGRect(x: 0, y: 0, width: 500, height: 20) - - appearance.backgroundColor = .systemBackground + appearance.backgroundColor = .secondarySystemGroupedBackground appearance.cornerRadius = 10 - appearance.shadowColor = .black - appearance.shadowOpacity = 0.2 - appearance.shadowRadius = 30 + appearance.shadowColor = UIColor(white: 0.5, alpha: 1) + appearance.shadowOpacity = 0.9 + appearance.shadowRadius = 25 appearance.animationduration = 0.25 appearance.textColor = .darkGray + appearance.setupMaskedCorners([.layerMaxXMaxYCorner, .layerMinXMaxYCorner]) let account = NCManageDatabase.shared.getTableAccount(account: metadata.account) let existingShares = NCManageDatabase.shared.getTableShares(metadata: metadata) @@ -278,10 +373,16 @@ extension NCShare: NCShareNetworkingDelegate { dropDown.dataSource.append(label) } - dropDown.anchorView = searchField - dropDown.bottomOffset = CGPoint(x: 10, y: searchField.bounds.height) - dropDown.width = searchField.bounds.width - 20 - dropDown.direction = .bottom + dropDown.anchorView = textField + dropDown.bottomOffset = CGPoint(x: 0, y: textField?.bounds.height ?? 0) + dropDown.width = textField?.bounds.width ?? 0 + if (UIDevice.current.userInterfaceIdiom == .phone || UIDevice.current.orientation.isLandscape), UIScreen.main.bounds.width < 1111 { + dropDown.topOffset = CGPoint(x: 0, y: -(textField?.bounds.height ?? 0)) + dropDown.direction = .top + } else { + dropDown.bottomOffset = CGPoint(x: 0, y: (textField?.bounds.height ?? 0) - 80) + dropDown.direction = .any + } dropDown.cellNib = UINib(nibName: "NCSearchUserDropDownCell", bundle: nil) dropDown.customCellConfiguration = { (index: Index, _, cell: DropDownCell) in @@ -291,6 +392,8 @@ extension NCShare: NCShareNetworkingDelegate { } dropDown.selectionAction = { index, _ in + self.textField?.text = "" + self.textField?.resignFirstResponder() let sharee = sharees[index] guard let advancePermission = UIStoryboard(name: "NCShare", bundle: nil).instantiateViewController(withIdentifier: "NCShareAdvancePermission") as? NCShareAdvancePermission, @@ -314,6 +417,19 @@ extension NCShare: NCShareNetworkingDelegate { func downloadLimitSet(to limit: Int, by token: String) { database.createDownloadLimit(account: metadata.account, count: 0, limit: limit, token: token) } + + func checkIsCollaboraFile() -> Bool { + guard let metadata = metadata else { + return false + } + + // EDITORS + let editors = utility.isDirectEditing(account: metadata.account, contentType: metadata.contentType) + let availableRichDocument = utility.isRichDocument(metadata) + + // RichDocument: Collabora + return (availableRichDocument && editors.count == 0) + } } // MARK: - UITableViewDelegate @@ -321,7 +437,8 @@ extension NCShare: NCShareNetworkingDelegate { extension NCShare: UITableViewDelegate { func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 60 +// return 60 + return indexPath.row == 0 ? UITableView.automaticDimension : 60 } } @@ -330,7 +447,7 @@ extension NCShare: UITableViewDelegate { extension NCShare: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { - return 2 + return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -343,10 +460,29 @@ extension NCShare: UITableViewDataSource { numRows = shares.firstShareLink != nil || canReshare ? 2 : 1 } } - return numRows + if let shareLink = shares.firstShareLink { + shares.share?.insert(shareLink, at: 0) + } + + if shares.share != nil { + numOfRows = shares.share!.count + } + return canReshare ? (numOfRows + 1) : numOfRows } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + if indexPath.row == 0 { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "NCShareEmailFieldCell", for: indexPath) as? NCShareEmailFieldCell else { return UITableViewCell() } + cell.searchField.addTarget(self, action: #selector(searchFieldDidEndOnExit(textField:)), for: .editingDidEndOnExit) + cell.searchField.addTarget(self, action: #selector(searchFieldDidChange(textField:)), for: .editingChanged) + cell.btnCreateLink.addTarget(self, action: #selector(createLinkClicked(_:)), for: .touchUpInside) + cell.btnContact.addTarget(self, action: #selector(selectContactClicked(_:)), for: .touchUpInside) + cell.labelNoShare.isHidden = (shares.share?.count ?? 0) > 0 + cell.heightLabelNoShare.constant = (shares.share?.count ?? 0) > 0 ? 0 : 25 + return cell + } + // Setup default share cells guard indexPath.section != 0 else { guard let cell = tableView.dequeueReusableCell(withIdentifier: "cellLink", for: indexPath) as? NCShareLinkCell @@ -364,63 +500,143 @@ extension NCShare: UITableViewDataSource { cell.isDirectory = metadata.directory cell.setupCellUI() shareLinksCount += 1 - return cell - } - - guard let tableShare = shares.share?[indexPath.row] else { return UITableViewCell() } - - // 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 + + let directory = self.metadata?.directory ?? false + guard let appDelegate = appDelegate, let tableShare = shares.share?[indexPath.row - 1] else { return UITableViewCell() } + + // // 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(titleAppendString: String(shareLinksCount)) + // if tableShare.shareType == shareCommon.SHARE_TYPE_LINK { shareLinksCount += 1 } + // return cell + + // LINK + if tableShare.shareType == shareCommon.SHARE_TYPE_LINK { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "cellLink", for: indexPath) as? NCShareLinkCell + else { return UITableViewCell() } cell.tableShare = tableShare - cell.isDirectory = metadata.directory cell.delegate = self - cell.setupCellUI(titleAppendString: String(shareLinksCount)) - if tableShare.shareType == shareCommon.SHARE_TYPE_LINK { shareLinksCount += 1 } + cell.setupCellUI() + if !tableShare.label.isEmpty { + cell.labelTitle.text = String(format: NSLocalizedString("_share_linklabel_", comment: ""), tableShare.label) + } else { + cell.labelTitle.text = directory ? NSLocalizedString("_share_link_folder_", comment: "") : NSLocalizedString("_share_link_file_", comment: "") + } + let isEditingAllowed = shareCommon.isEditingEnabled(isDirectory: directory, fileExtension: metadata?.fileExtension ?? "", shareType: tableShare.shareType) + if isEditingAllowed || directory || checkIsCollaboraFile() { + cell.btnQuickStatus.isEnabled = true + cell.labelQuickStatus.textColor = NCBrandColor.shared.brand + cell.imageDownArrow.image = UIImage(named: "downArrow")?.imageColor(NCBrandColor.shared.brand) + } else { + cell.btnQuickStatus.isEnabled = false + cell.labelQuickStatus.textColor = NCBrandColor.shared.optionItem + cell.imageDownArrow.image = UIImage(named: "downArrow")?.imageColor(NCBrandColor.shared.optionItem) + } + return cell + } else { + // USER / GROUP etc. + if let cell = tableView.dequeueReusableCell(withIdentifier: "cellUser", for: indexPath) as? NCShareUserCell { + cell.tableShare = tableShare + cell.isDirectory = metadata.directory + cell.delegate = self + cell.setupCellUI(userId: session.userId, session: session, metadata: metadata) + // cell.setupCellUI(userId: appDelegate.userId) + let isEditingAllowed = shareCommon.isEditingEnabled(isDirectory: directory, fileExtension: metadata?.fileExtension ?? "", shareType: tableShare.shareType) + if isEditingAllowed || checkIsCollaboraFile() { + cell.btnQuickStatus.isEnabled = true + } else { + cell.btnQuickStatus.isEnabled = false + cell.labelQuickStatus.textColor = NCBrandColor.shared.optionItem + cell.imageDownArrow.image = UIImage(named: "downArrow")?.imageColor(NCBrandColor.shared.optionItem) + } + return cell + } } + } + return UITableViewCell() + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + guard let headerView = Bundle.main.loadNibNamed("NCShareHeaderView", owner: self, options: nil)?.first as? NCShareHeaderView else { + return UIView() + } + headerView.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + headerView.fileName.textColor = NCBrandColor.shared.label + headerView.labelSharing.textColor = NCBrandColor.shared.label + headerView.labelSharingInfo.textColor = NCBrandColor.shared.label + headerView.info.textColor = NCBrandColor.shared.systemGray2 + headerView.ocId = metadata!.ocId + headerView.updateCanReshareUI() + + + if FileManager.default.fileExists(atPath: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata?.ocId ?? "", etag: metadata?.etag ?? "")) { + headerView.fullWidthImageView.image = UIImage(contentsOfFile: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata?.ocId ?? "", etag: metadata?.etag ?? "")) + headerView.fullWidthImageView.contentMode = .scaleAspectFill + headerView.imageView.isHidden = true } else { - // USER / GROUP etc. - 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, session: session, metadata: metadata) - - return cell + if metadata?.directory ?? false { + let image = (metadata?.e2eEncrypted ?? false) ? UIImage(named: "folderEncrypted") : UIImage(named: "folder_nmcloud") + headerView.imageView.image = image + } else if !(metadata?.iconName.isEmpty ?? false) { + headerView.imageView.image = metadata!.fileExtension == "odg" ? UIImage(named: "file-diagram") : UIImage.init(named: metadata!.iconName) + } else { + headerView.imageView.image = UIImage(named: "file") } } - - return UITableViewCell() + + headerView.fileName.text = metadata?.fileNameView + headerView.fileName.textColor = NCBrandColor.shared.label + if metadata!.favorite { + headerView.favorite.setImage(utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite, size: 24), for: .normal) + } else { + headerView.favorite.setImage(utility.loadImage(named: "star.fill", color: NCBrandColor.shared.textInfo, size: 24), for: .normal) + } + headerView.info.text = utilityFileSystem.transformedSize(metadata!.size) + ", " + utility.dateDiff(metadata!.date as Date) + return headerView + + } + func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat { + return metadata?.ownerId != appDelegate?.userId ? canReshare ? 400 : 350 : 320 + } + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return metadata?.ownerId != appDelegate?.userId ? canReshare ? UITableView.automaticDimension : 350 : 320 } } -// MARK: - CNContactPickerDelegate +//MARK: CNContactPickerDelegate extension NCShare: CNContactPickerDelegate { func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) { if contact.emailAddresses.count > 1 { showEmailList(arrEmail: contact.emailAddresses.map({$0.value as String}), sender: picker) } else if let email = contact.emailAddresses.first?.value as? String { - searchField?.text = email + textField?.text = email networking?.getSharees(searchString: email) } } - - func showEmailList(arrEmail: [String], sender: Any?) { + + func contactPickerDidCancel(_ picker: CNContactPickerViewController) { + self.keyboardWillHide(notification: Notification(name: Notification.Name("dismiss"))) + } + + func showEmailList(arrEmail: [String]) { var actions = [NCMenuAction]() for email in arrEmail { actions.append( NCMenuAction( title: email, - icon: utility.loadImage(named: "email", colors: [NCBrandColor.shared.iconImageColor]), + icon: utility.loadImage(named: "email").imageColor(NCBrandColor.shared.brandElement), selected: false, on: false, sender: sender, action: { _ in - self.searchField?.text = email + self.textField?.text = email self.networking?.getSharees(searchString: email) } ) diff --git a/iOSClient/Share/NCShareCommon.swift b/iOSClient/Share/NCShareCommon.swift index 5f38d26281..09c8de9495 100644 --- a/iOSClient/Share/NCShareCommon.swift +++ b/iOSClient/Share/NCShareCommon.swift @@ -43,7 +43,7 @@ class NCShareCommon: NSObject { let size: CGFloat = 200 let bottomImage = UIImage(named: "circle_fill")!.image(color: colorCircle, size: size / 2) - let topImage = NCUtility().loadImage(named: imageName, colors: [NCBrandColor.shared.iconImageColor]) + let topImage = UIImage(named: imageName)!.image(color: .white, size: size / 2) UIGraphicsBeginImageContextWithOptions(CGSize(width: size, height: size), false, UIScreen.main.scale) bottomImage.draw(in: CGRect(origin: CGPoint.zero, size: CGSize(width: size, height: size))) topImage.draw(in: CGRect(origin: CGPoint(x: size / 4, y: size / 4), size: CGSize(width: size / 2, height: size / 2))) @@ -69,31 +69,80 @@ class NCShareCommon: NSObject { } } - func getImageShareType(shareType: Int) -> UIImage? { - + func getImageShareType(shareType: Int, isDropDown:Bool = false) -> UIImage? { + switch shareType { case SHARE_TYPE_USER: - return UIImage(named: "shareTypeUser")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) + return UIImage(named: "shareTypeEmail")?.imageColor(NCBrandColor.shared.label) case self.SHARE_TYPE_GROUP: - return UIImage(named: "shareTypeGroup")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) + return UIImage(named: "shareTypeGroup")?.imageColor(NCBrandColor.shared.label) case self.SHARE_TYPE_LINK: - return UIImage(named: "shareTypeLink")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) + return UIImage(named: "shareTypeLink")?.imageColor(NCBrandColor.shared.label) case self.SHARE_TYPE_EMAIL: - return UIImage(named: "shareTypeEmail")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) + return UIImage(named: isDropDown ? "email" : "shareTypeUser")?.imageColor(NCBrandColor.shared.label) case self.SHARE_TYPE_CONTACT: return UIImage(named: "shareTypeUser")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) case self.SHARE_TYPE_FEDERATED: return UIImage(named: "shareTypeUser")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) +// return UIImage(named: "shareTypeUser")?.imageColor(NCBrandColor.shared.label) + case self.SHARE_TYPE_REMOTE: + return UIImage(named: isDropDown ? "shareTypeUser" : "shareTypeEmail")?.imageColor(NCBrandColor.shared.label) case self.SHARE_TYPE_CIRCLE: - return UIImage(named: "shareTypeCircles")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) + return UIImage(named: "shareTypeCircles")?.imageColor(NCBrandColor.shared.label) case self.SHARE_TYPE_GUEST: return UIImage(named: "shareTypeUser")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) case self.SHARE_TYPE_FEDERATED_GROUP: return UIImage(named: "shareTypeGroup")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) +// return UIImage(named: "shareTypeUser")?.imageColor(NCBrandColor.shared.label) + case self.SHARE_TYPE_REMOTE_GROUP: + return UIImage(named: "shareTypeGroup")?.imageColor(NCBrandColor.shared.label) case self.SHARE_TYPE_ROOM: - return UIImage(named: "shareTypeRoom")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) + return UIImage(named: "shareTypeRoom")?.imageColor(NCBrandColor.shared.label) default: - return UIImage(named: "shareTypeUser")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) + return UIImage(named: "shareTypeUser")?.imageColor(NCBrandColor.shared.label) } } + + func isLinkShare(shareType: Int) -> Bool { + return shareType == SHARE_TYPE_LINK + } + + func isExternalUserShare(shareType: Int) -> Bool { + return shareType == SHARE_TYPE_EMAIL + } + + func isInternalUser(shareType: Int) -> Bool { + return shareType == SHARE_TYPE_USER + } + + func isFileTypeAllowedForEditing(fileExtension: String, shareType: Int) -> Bool { + if fileExtension == "md" || fileExtension == "txt" { + return true + } else { + return isInternalUser(shareType: shareType) + } + } + + func isEditingEnabled(isDirectory: Bool, fileExtension: String, shareType: Int) -> Bool { + if !isDirectory {//file + return isFileTypeAllowedForEditing(fileExtension: fileExtension, shareType: shareType) + } else { + return true + } + } + + func isFileDropOptionVisible(isDirectory: Bool, shareType: Int) -> Bool { + return (isDirectory && (isLinkShare(shareType: shareType) || isExternalUserShare(shareType: shareType))) + } + + func isCurrentUserIsFileOwner(fileOwnerId: String) -> Bool { + if let currentUser = NCManageDatabase.shared.getActiveAccount(), currentUser.userId == fileOwnerId { + return true + } + return false + } + + func canReshare(withPermission permission: String) -> Bool { + return permission.contains(NCGlobal.shared.permissionCanShare) + } } diff --git a/iOSClient/Share/NCShareEmailFieldCell.swift b/iOSClient/Share/NCShareEmailFieldCell.swift new file mode 100644 index 0000000000..c39be3b210 --- /dev/null +++ b/iOSClient/Share/NCShareEmailFieldCell.swift @@ -0,0 +1,89 @@ +// +// NCShareEmailFieldCell.swift +// Nextcloud +// +// Created by A200020526 on 01/06/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit + +enum Tag { + static let searchField = 999 +} + +class NCShareEmailFieldCell: UITableViewCell { + @IBOutlet weak var searchField: UITextField! + @IBOutlet weak var btnCreateLink: UIButton! + @IBOutlet weak var labelYourShare: UILabel! + @IBOutlet weak var labelShareByMail: UILabel! + @IBOutlet weak var btnContact: UIButton! + @IBOutlet weak var labelNoShare: UILabel! + @IBOutlet weak var heightLabelNoShare: NSLayoutConstraint! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + setupCell() + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + + func setupCell(){ + self.btnCreateLink.setTitle(NSLocalizedString("_create_link_", comment: ""), for: .normal) + self.btnCreateLink.layer.cornerRadius = 7 + self.btnCreateLink.layer.masksToBounds = true + self.btnCreateLink.layer.borderWidth = 1 + self.btnCreateLink.titleLabel?.font = UIFont.systemFont(ofSize: 17) + self.btnCreateLink.titleLabel!.adjustsFontSizeToFitWidth = true + self.btnCreateLink.titleLabel!.minimumScaleFactor = 0.5 + self.btnCreateLink.layer.borderColor = NCBrandColor.shared.label.cgColor + self.btnCreateLink.setTitleColor(NCBrandColor.shared.label, for: .normal) + self.btnCreateLink.backgroundColor = .clear + + self.labelShareByMail.text = NSLocalizedString("personal_share_by_mail", comment: "") + self.labelShareByMail.textColor = NCBrandColor.shared.shareByEmailTextColor + + labelYourShare.text = NSLocalizedString("_your_shares_", comment: "") + + searchField.layer.cornerRadius = 5 + searchField.layer.masksToBounds = true + searchField.layer.borderWidth = 1 + self.searchField.text = "" + searchField.attributedPlaceholder = NSAttributedString(string: NSLocalizedString("_shareLinksearch_placeholder_", comment: ""), + attributes: [NSAttributedString.Key.foregroundColor: NCBrandColor.shared.gray60]) + searchField.textColor = NCBrandColor.shared.label + searchField.layer.borderColor = NCBrandColor.shared.label.cgColor + searchField.tag = Tag.searchField + setDoneButton(sender: searchField) + + btnContact.layer.cornerRadius = 5 + btnContact.layer.masksToBounds = true + btnContact.layer.borderWidth = 1 + btnContact.layer.borderColor = NCBrandColor.shared.label.cgColor + btnContact.tintColor = NCBrandColor.shared.label + btnContact.setImage(NCUtility().loadImage(named: "contact").image(color: NCBrandColor.shared.label, size: 24), for: .normal) + labelNoShare.textColor = NCBrandColor.shared.textInfo + labelNoShare.numberOfLines = 0 + labelNoShare.font = UIFont.systemFont(ofSize: 17) + labelNoShare.text = NSLocalizedString("no_shares_created", comment: "") + } + + @objc func cancelDatePicker() { + self.searchField.endEditing(true) + } + + func setDoneButton(sender: UITextField) { + //ToolBar + let toolbar = UIToolbar(); + toolbar.sizeToFit() + let doneButton = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: .plain, target: self, action: #selector(cancelDatePicker)); + let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil) + toolbar.setItems([spaceButton, doneButton], animated: false) + sender.inputAccessoryView = toolbar + } +} diff --git a/iOSClient/Share/NCShareEmailFieldCell.xib b/iOSClient/Share/NCShareEmailFieldCell.xib new file mode 100644 index 0000000000..72cbdfe2d3 --- /dev/null +++ b/iOSClient/Share/NCShareEmailFieldCell.xib @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Share/NCShareHeader.swift b/iOSClient/Share/NCShareHeader.swift index a18f2b6114..749197c7af 100644 --- a/iOSClient/Share/NCShareHeader.swift +++ b/iOSClient/Share/NCShareHeader.swift @@ -1,5 +1,5 @@ // -// NCShareHeader.swift +// NCShareAdvancePermissionHeader.swift // Nextcloud // // Created by T-systems on 10/08/21. @@ -22,52 +22,61 @@ // import UIKit -import TagListView -class NCShareHeader: UIView { +class NCShareAdvancePermissionHeader: UIView { @IBOutlet weak var imageView: UIImageView! @IBOutlet weak var fileName: UILabel! @IBOutlet weak var info: UILabel! + @IBOutlet weak var favorite: UIButton! @IBOutlet weak var fullWidthImageView: UIImageView! - @IBOutlet weak var fileNameTopConstraint: NSLayoutConstraint! - @IBOutlet weak var tagListView: TagListView! - - private var heightConstraintWithImage: NSLayoutConstraint? - private var heightConstraintWithoutImage: NSLayoutConstraint? - + var ocId = "" + let utility = NCUtility() + let utilityFileSystem = NCUtilityFileSystem() + func setupUI(with metadata: tableMetadata) { - let utilityFileSystem = NCUtilityFileSystem() - if let image = NCUtility().getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt1024) { - fullWidthImageView.image = image + backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + fileName.textColor = NCBrandColor.shared.label + info.textColor = NCBrandColor.shared.textInfo + backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + if FileManager.default.fileExists(atPath: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)) { + fullWidthImageView.image = utility.getImageMetadata(metadata, for: frame.height) fullWidthImageView.contentMode = .scaleAspectFill - imageView.image = fullWidthImageView.image imageView.isHidden = true } else { if metadata.directory { - imageView.image = metadata.e2eEncrypted ? NCImageCache.shared.getFolderEncrypted(account: metadata.account) : NCImageCache.shared.getFolder(account: metadata.account) + imageView.image = UIImage.init(named: "folder") } else if !metadata.iconName.isEmpty { - imageView.image = NCUtility().loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) + imageView.image = UIImage.init(named: metadata.iconName) } else { - imageView.image = NCImageCache.shared.getImageFile() + imageView.image = UIImage.init(named: "file") } - - fileNameTopConstraint.constant -= 45 } - + favorite.setNeedsUpdateConstraints() + favorite.layoutIfNeeded() fileName.text = metadata.fileNameView - fileName.textColor = NCBrandColor.shared.textColor - info.textColor = NCBrandColor.shared.textColor2 - info.text = utilityFileSystem.transformedSize(metadata.size) + ", " + NCUtility().getRelativeDateTitle(metadata.date as Date) - - tagListView.addTags(Array(metadata.tags)) - - setNeedsLayout() - layoutIfNeeded() + fileName.textColor = NCBrandColor.shared.fileFolderName + if metadata.favorite { + favorite.setImage(utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite, size: 24), for: .normal) + } else { + favorite.setImage(utility.loadImage(named: "star.fill", color: NCBrandColor.shared.textInfo, size: 24), for: .normal) + } + info.textColor = NCBrandColor.shared.optionItem + info.text = utilityFileSystem.transformedSize(metadata.size) + ", " + utility.dateDiff(metadata.date as Date) } - - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - if fullWidthImageView.image != nil { - imageView.isHidden = traitCollection.verticalSizeClass != .compact + + @IBAction func touchUpInsideFavorite(_ sender: UIButton) { + guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { return } + NCNetworking.shared.favoriteMetadata(metadata) { error in + if error == .success { + guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) else { return } + if metadata.favorite { + self.favorite.setImage(self.utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite, size: 24), for: .normal) + } else { + self.favorite.setImage(self.utility.loadImage(named: "star.fill", color: NCBrandColor.shared.textInfo, size: 24), for: .normal) + } + } else { + NCContentPresenter().showError(error: error) + } } } } diff --git a/iOSClient/Share/NCShareHeaderView.xib b/iOSClient/Share/NCShareHeaderView.xib index 44b83d3fc7..b7384fb2ec 100644 --- a/iOSClient/Share/NCShareHeaderView.xib +++ b/iOSClient/Share/NCShareHeaderView.xib @@ -1,149 +1,176 @@ - + - - + - - + + - - + + - - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + - + - - - - - - - - - - - - - - - + + diff --git a/iOSClient/Share/NCShareLinkCell.swift b/iOSClient/Share/NCShareLinkCell.swift index 60787ab5fd..6d90546526 100644 --- a/iOSClient/Share/NCShareLinkCell.swift +++ b/iOSClient/Share/NCShareLinkCell.swift @@ -33,6 +33,16 @@ class NCShareLinkCell: UITableViewCell { @IBOutlet private weak var copyButton: UIButton! @IBOutlet weak var imageDownArrow: UIImageView! +// @IBOutlet weak var imageItem: UIImageView! +// @IBOutlet weak var labelTitle: UILabel! +// @IBOutlet weak var buttonCopy: UIButton! +// @IBOutlet weak var buttonMenu: UIButton! +// @IBOutlet weak var status: UILabel! +// @IBOutlet weak var btnQuickStatus: UIButton! +// @IBOutlet weak var imageDownArrow: UIImageView! +// @IBOutlet weak var labelQuickStatus: UILabel! + + private let iconShare: CGFloat = 200 var tableShare: tableShare? var isDirectory = false weak var delegate: NCShareLinkCellDelegate? @@ -49,7 +59,7 @@ class NCShareLinkCell: UITableViewCell { func setupCellUI(titleAppendString: String? = nil) { var menuImageName = "ellipsis" let permissions = NCPermissions() - + menuButton.isHidden = isInternalLink descriptionLabel.isHidden = !isInternalLink copyButton.isHidden = !isInternalLink && tableShare == nil @@ -60,21 +70,21 @@ class NCShareLinkCell: UITableViewCell { copyButton.setImage(UIImage(systemName: "doc.on.doc")?.withTintColor(.label, renderingMode: .alwaysOriginal), for: .normal) } copyButton.accessibilityLabel = NSLocalizedString("_copy_", comment: "") - + menuButton.accessibilityLabel = NSLocalizedString("_more_", comment: "") menuButton.accessibilityIdentifier = "showShareLinkDetails" - + if isInternalLink { labelTitle.text = NSLocalizedString("_share_internal_link_", comment: "") descriptionLabel.text = NSLocalizedString("_share_internal_link_des_", comment: "") 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))" @@ -84,19 +94,19 @@ class NCShareLinkCell: UITableViewCell { menuButton.accessibilityLabel = NSLocalizedString("_add_", comment: "") menuButton.accessibilityIdentifier = "addShareLink" } - + imageItem.image = NCUtility().loadImage(named: "link.circle.fill", colors: [NCBrandColor.shared.getElement(account: tableShare?.account)]) menuButton.setImage(NCUtility().loadImage(named: menuImageName, colors: [NCBrandColor.shared.iconImageColor]), for: .normal) } - + 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: "") } @@ -106,27 +116,58 @@ class NCShareLinkCell: UITableViewCell { 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) { - delegate?.tapCopy(with: tableShare, sender: sender) + override func awakeFromNib() { + super.awakeFromNib() + + imageItem.image = UIImage(named: "sharebylink")?.image(color: NCBrandColor.shared.label, size: 30) + buttonCopy.setImage(UIImage.init(named: "shareCopy")!.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) + buttonMenu.setImage(UIImage.init(named: "shareMenu")!.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) + labelQuickStatus.textColor = NCBrandColor.shared.customer } - @IBAction func touchUpMenu(_ sender: Any) { + func setupCellUI() { + guard let tableShare = tableShare else { + return + } + contentView.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + labelTitle.textColor = NCBrandColor.shared.label + + if tableShare.permissions == NCGlobal.shared.permissionCreateShare { + labelQuickStatus.text = NSLocalizedString("_share_file_drop_", comment: "") + } else { + // Read Only + if CCUtility.isAnyPermission(toEdit: tableShare.permissions) { + labelQuickStatus.text = NSLocalizedString("_share_editing_", comment: "") + } else { + labelQuickStatus.text = NSLocalizedString("_share_read_only_", comment: "") + } + } + } + + @IBAction func touchUpInsideCopy(_ sender: Any) { + delegate?.tapCopy(with: tableShare, sender: sender) + } + + @IBAction func touchUpInsideMenu(_ sender: Any) { delegate?.tapMenu(with: tableShare, sender: sender) } @objc func openQuickStatus(_ sender: UITapGestureRecognizer) { + } + + @IBAction func quickStatusClicked(_ sender: UIButton) { delegate?.quickStatus(with: tableShare, sender: sender) } } diff --git a/iOSClient/Share/NCShareNetworking.swift b/iOSClient/Share/NCShareNetworking.swift index f3bf5d6b24..0e9067d6a4 100644 --- a/iOSClient/Share/NCShareNetworking.swift +++ b/iOSClient/Share/NCShareNetworking.swift @@ -100,6 +100,21 @@ class NCShareNetworking: NSObject { } } } + + func createShareLink(password: String?) { + NCActivityIndicator.shared.start(backgroundView: view) + let filenamePath = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId) + NextcloudKit.shared.createShareLink(path: filenamePath) { [self] account, share, data, error in + NCActivityIndicator.shared.stop() + if error == .success && share != nil { + let home = utilityFileSystem.getHomeServer(urlBase: self.metadata.urlBase, userId: self.metadata.userId) + NCManageDatabase.shared.addShare(account: self.metadata.account, home:home, shares: [share!]) + } else if error != .success{ + NCContentPresenter().showError(error: error) + } + self.delegate?.shareCompleted() + } + } func createShare(_ template: Shareable, downloadLimit: DownloadLimitViewModel) { // NOTE: Permissions don't work for creating with file drop! diff --git a/iOSClient/Share/NCSharePaging.swift b/iOSClient/Share/NCSharePaging.swift index 11d0aa2605..2ee6b5edd1 100644 --- a/iOSClient/Share/NCSharePaging.swift +++ b/iOSClient/Share/NCSharePaging.swift @@ -29,7 +29,7 @@ import MarqueeLabel import TagListView protocol NCSharePagingContent { - var textField: UIView? { get } + var textField: UITextField? { get } } class NCSharePaging: UIViewController { @@ -52,19 +52,17 @@ class NCSharePaging: UIViewController { navigationController?.navigationBar.tintColor = NCBrandColor.shared.iconImageColor navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_close_", comment: ""), style: .done, target: self, action: #selector(exitTapped(_:))) - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground(notification:)), name: UIApplication.didEnterBackgroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground(notification:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidEnterBackground), object: nil) // *** MUST BE THE FIRST ONE *** pagingViewController.metadata = metadata pagingViewController.backgroundColor = .systemBackground pagingViewController.menuBackgroundColor = .systemBackground pagingViewController.selectedBackgroundColor = .systemBackground - pagingViewController.indicatorColor = NCBrandColor.shared.getElement(account: metadata.account) - pagingViewController.textColor = NCBrandColor.shared.textColor - pagingViewController.selectedTextColor = NCBrandColor.shared.getElement(account: metadata.account) + pagingViewController.textColor = .label + pagingViewController.selectedTextColor = .label // Pagination addChild(pagingViewController) @@ -76,16 +74,14 @@ class NCSharePaging: UIViewController { height: 1, zIndex: Int.max, spacing: .zero, - insets: .zero + insets: UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5) ) - pagingViewController.borderOptions = .visible(height: 1, zIndex: Int.max, insets: .zero) - // Contrain the paging view to all edges. pagingViewController.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ pagingViewController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - pagingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + pagingViewController.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), pagingViewController.view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), pagingViewController.view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor) ]) @@ -99,6 +95,7 @@ class NCSharePaging: UIViewController { pagingViewController.select(index: 0) } + (pagingViewController.view as? NCSharePagingView)?.setupConstraints() pagingViewController.reloadMenu() } @@ -115,9 +112,9 @@ class NCSharePaging: UIViewController { self.dismiss(animated: false, completion: nil) } -// pagingViewController.menuItemSize = .fixed( -// width: self.view.bounds.width / CGFloat(self.pages.count), -// height: 40) + pagingViewController.menuItemSize = .fixed( + width: self.view.bounds.width / CGFloat(self.pages.count), + height: 40) } override func viewWillDisappear(_ animated: Bool) { @@ -130,12 +127,17 @@ class NCSharePaging: UIViewController { deinit { NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidShowNotification, object: nil) NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) - NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) - self.currentVC?.textField?.resignFirstResponder() + + coordinator.animate(alongsideTransition: nil) { _ in + self.pagingViewController.menuItemSize = .fixed( + width: self.view.bounds.width / CGFloat(self.pages.count), + height: 40) + self.currentVC?.textField?.resignFirstResponder() + } } // MARK: - NotificationCenter & Keyboard & TextField @@ -185,7 +187,7 @@ extension NCSharePaging: PagingViewControllerDataSource { func pagingViewController(_: PagingViewController, viewControllerAt index: Int) -> UIViewController { - let height: CGFloat = 50 + let height = pagingViewController.options.menuHeight + NCSharePagingView.headerHeight + NCSharePagingView.tagHeaderHeight if pages[index] == .activity { guard let viewController = UIStoryboard(name: "NCActivity", bundle: nil).instantiateInitialViewController() as? NCActivity else { @@ -243,11 +245,11 @@ class NCShareHeaderViewController: PagingViewController { } class NCSharePagingView: PagingView { + + static let headerHeight: CGFloat = 90 + static var tagHeaderHeight: CGFloat = 0 var metadata = tableMetadata() - let utilityFileSystem = NCUtilityFileSystem() - let utility = NCUtility() public var headerHeightConstraint: NSLayoutConstraint? - var header: NCShareHeader? // MARK: - View Life Cycle @@ -260,71 +262,74 @@ class NCSharePagingView: PagingView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - override func setupConstraints() { - guard let headerView = Bundle.main.loadNibNamed("NCShareHeader", owner: self, options: nil)?.first as? NCShareHeader else { return } - header = headerView - headerView.backgroundColor = .systemBackground - - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .short - dateFormatter.timeStyle = .short - dateFormatter.locale = Locale.current - - headerView.setupUI(with: metadata) - - addSubview(headerView) - - collectionView.translatesAutoresizingMaskIntoConstraints = false - headerView.translatesAutoresizingMaskIntoConstraints = false - pageView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - collectionView.leadingAnchor.constraint(equalTo: leadingAnchor), - collectionView.trailingAnchor.constraint(equalTo: trailingAnchor), - collectionView.heightAnchor.constraint(equalToConstant: options.menuHeight), - collectionView.topAnchor.constraint(equalTo: headerView.bottomAnchor), - - headerView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), - headerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), - headerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), - - pageView.leadingAnchor.constraint(equalTo: leadingAnchor), - pageView.trailingAnchor.constraint(equalTo: trailingAnchor), - pageView.bottomAnchor.constraint(equalTo: bottomAnchor), - pageView.topAnchor.constraint(equalTo: headerView.bottomAnchor) - ]) - } } class NCShareHeaderView: UIView { @IBOutlet weak var imageView: UIImageView! - @IBOutlet weak var path: MarqueeLabel! + @IBOutlet weak var fileName: UILabel! @IBOutlet weak var info: UILabel! - @IBOutlet weak var creation: UILabel! - @IBOutlet weak var upload: UILabel! @IBOutlet weak var favorite: UIButton! - @IBOutlet weak var details: UIButton! - @IBOutlet weak var tagListView: TagListView! - + @IBOutlet weak var labelSharing: UILabel! + @IBOutlet weak var labelSharingInfo: UILabel! + @IBOutlet weak var fullWidthImageView: UIImageView! + @IBOutlet weak var canShareInfoView: UIView! + @IBOutlet weak var sharedByLabel: UILabel! + @IBOutlet weak var resharingAllowedLabel: UILabel! + @IBOutlet weak var sharedByImageView: UIImageView! + @IBOutlet weak var constraintTopSharingLabel: NSLayoutConstraint! + let utility = NCUtility() var ocId = "" override func awakeFromNib() { super.awakeFromNib() let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(longTap(_:))) path.addGestureRecognizer(longGesture) + setupUI() } + func setupUI() { + labelSharing.text = NSLocalizedString("_sharing_", comment: "") + labelSharingInfo.text = NSLocalizedString("_sharing_message_", comment: "") + + if UIScreen.main.bounds.width < 376 { + constraintTopSharingLabel.constant = 15 + } + } + + func updateCanReshareUI() { + let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) + var isCurrentUser = true + if let ownerId = metadata?.ownerId, !ownerId.isEmpty { + isCurrentUser = NCShareCommon().isCurrentUserIsFileOwner(fileOwnerId: ownerId) + } + var canReshare: Bool { + guard let metadata = metadata else { return true } + return ((metadata.sharePermissionsCollaborationServices & NCGlobal.shared.permissionShareShare) != 0) + } + canShareInfoView.isHidden = isCurrentUser + labelSharingInfo.isHidden = !isCurrentUser + + if !isCurrentUser { + sharedByImageView.image = UIImage(named: "cloudUpload")?.image(color: .systemBlue, size: 26) + let ownerName = metadata?.ownerDisplayName ?? "" + sharedByLabel.text = NSLocalizedString("_shared_with_you_by_", comment: "") + " " + ownerName + let resharingAllowedMessage = NSLocalizedString("_share_reshare_allowed_", comment: "") + " " + NSLocalizedString("_sharing_message_", comment: "") + let resharingNotAllowedMessage = NSLocalizedString("_share_reshare_not_allowed_", comment: "") + resharingAllowedLabel.text = canReshare ? resharingAllowedMessage : resharingNotAllowedMessage + } + } + @IBAction func touchUpInsideFavorite(_ sender: UIButton) { guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { return } NCNetworking.shared.favoriteMetadata(metadata) { error in if error == .success { guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) else { return } - self.favorite.setImage(NCUtility().loadImage( - named: "star.fill", - colors: metadata.favorite ? [NCBrandColor.shared.yellowFavorite] : [NCBrandColor.shared.iconImageColor2], - size: 20), for: .normal) + if metadata.favorite { + self.favorite.setImage(self.utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite, size: 24), for: .normal) + } else { + self.favorite.setImage(self.utility.loadImage(named: "star.fill", color: NCBrandColor.shared.textInfo, size: 24), for: .normal) + } } else { NCContentPresenter().showError(error: error) } diff --git a/iOSClient/Share/NCShareUserCell.swift b/iOSClient/Share/NCShareUserCell.swift index d746ef46d1..ddc9c241b2 100644 --- a/iOSClient/Share/NCShareUserCell.swift +++ b/iOSClient/Share/NCShareUserCell.swift @@ -34,7 +34,8 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { @IBOutlet weak var btnQuickStatus: UIButton! @IBOutlet weak var labelQuickStatus: UILabel! @IBOutlet weak var imageDownArrow: UIImageView! - + @IBOutlet weak var labelCanEdit: UILabel! + @IBOutlet weak var switchCanEdit: UISwitch! private var index = IndexPath() var tableShare: tableShare? @@ -46,9 +47,6 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { get { return index } set { index = newValue } } - var fileAvatarImageView: UIImageView? { - return imageItem - } var fileUser: String? { get { return tableShare?.shareWith } set {} @@ -72,28 +70,41 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { labelTitle.lineBreakMode = .byTruncatingMiddle labelTitle.textColor = NCBrandColor.shared.textColor + contentView.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + + labelTitle.text = tableShare.shareWithDisplayname + labelTitle.textColor = NCBrandColor.shared.label isUserInteractionEnabled = true - labelQuickStatus.isHidden = false - imageDownArrow.isHidden = false + switchCanEdit.isHidden = true + labelCanEdit.isHidden = true buttonMenu.isHidden = false buttonMenu.accessibilityLabel = NSLocalizedString("_more_", comment: "") imageItem.image = NCShareCommon().getImageShareType(shareType: tableShare.shareType) let status = utility.getUserStatus(userIcon: tableShare.userIcon, userStatus: tableShare.userStatus, userMessage: tableShare.userMessage) - imageStatus.image = status.statusImage + imageStatus.image = status.onlineStatus self.status.text = status.statusMessage + + if CCUtility.isAnyPermission(toEdit: tableShare.permissions) { + switchCanEdit.setOn(true, animated: false) + } else { + switchCanEdit.setOn(false, animated: false) + } // If the initiator or the recipient is not the current user, show the list of sharees without any options to edit it. if tableShare.uidOwner != userId && tableShare.uidFileOwner != userId { isUserInteractionEnabled = false - labelQuickStatus.isHidden = true - imageDownArrow.isHidden = true + switchCanEdit.isHidden = true + labelCanEdit.isHidden = true buttonMenu.isHidden = true } btnQuickStatus.accessibilityHint = NSLocalizedString("_user_sharee_footer_", comment: "") btnQuickStatus.setTitle("", for: .normal) btnQuickStatus.contentHorizontalAlignment = .left + btnQuickStatus.isEnabled = true + labelQuickStatus.textColor = NCBrandColor.shared.brand + imageDownArrow.image = UIImage(named: "downArrow")?.imageColor(NCBrandColor.shared.brand) if permissions.canEdit(tableShare.permissions, isDirectory: isDirectory) { // Can edit labelQuickStatus.text = NSLocalizedString("_share_editing_", comment: "") @@ -131,15 +142,27 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { default: return "" } + if tableShare.permissions == NCGlobal.shared.permissionCreateShare { + labelQuickStatus.text = NSLocalizedString("_share_file_drop_", comment: "") + } else { + // Read Only + if CCUtility.isAnyPermission(toEdit: tableShare.permissions) { + labelQuickStatus.text = NSLocalizedString("_share_editing_", comment: "") + } else { + labelQuickStatus.text = NSLocalizedString("_share_read_only_", comment: "") + } + } } override func awakeFromNib() { super.awakeFromNib() let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapAvatarImage(_:))) imageItem?.addGestureRecognizer(tapGesture) - + buttonMenu.setImage(UIImage.init(named: "shareMenu")!.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) labelQuickStatus.textColor = NCBrandColor.shared.customer - imageDownArrow.image = utility.loadImage(named: "arrowtriangle.down.circle", colors: [NCBrandColor.shared.customer]) + imageDownArrow.image = UIImage(named: "downArrow")?.imageColor(NCBrandColor.shared.customer) + switchCanEdit.transform = CGAffineTransform(scaleX: 0.75, y: 0.75) + switchCanEdit.onTintColor = NCBrandColor.shared.brandElement } @objc func tapAvatarImage(_ sender: UITapGestureRecognizer) { @@ -188,15 +211,9 @@ class NCSearchUserDropDownCell: DropDownCell, NCCellProtocol { func setupCell(sharee: NKSharee, session: NCSession.Session) { let utility = NCUtility() - imageItem.image = NCShareCommon().getImageShareType(shareType: sharee.shareType) - imageShareeType.image = NCShareCommon().getImageShareType(shareType: sharee.shareType) + imageShareeType.image = NCShareCommon().getImageShareType(shareType: sharee.shareType, isDropDown: true) let status = utility.getUserStatus(userIcon: sharee.userIcon, userStatus: sharee.userStatus, userMessage: sharee.userMessage) - - if let statusImage = status.statusImage { - imageStatus.image = statusImage - imageStatus.makeCircularBackground(withColor: .systemBackground) - } - + imageStatus.image = status.onlineStatus self.status.text = status.statusMessage if self.status.text?.count ?? 0 > 0 { centerTitle.constant = -5 diff --git a/iOSClient/Share/ShareDownloadLimitNetwork.swift b/iOSClient/Share/ShareDownloadLimitNetwork.swift new file mode 100644 index 0000000000..9fadc08515 --- /dev/null +++ b/iOSClient/Share/ShareDownloadLimitNetwork.swift @@ -0,0 +1,146 @@ +// +// ShareDownloadLimitNetwork.swift +// Nextcloud +// +// Created by A118830248 on 11/11/21. +// Copyright © 2021 Marino Faggiana. All rights reserved. +// + +import Foundation +import SwiftyJSON +import NextcloudKit +import Alamofire + +class NMCCommunication: NSObject, XMLParserDelegate { + + public static let shared: NMCCommunication = { + let instance = NMCCommunication() + return instance + }() + + var message = "" + var foundCharacters = ""; + var downloadLimit = DownloadLimit() + private lazy var appDelegate = UIApplication.shared.delegate as? AppDelegate + + func getDownloadLimit(token: String, completion: @escaping (_ downloadLimit: DownloadLimit?, _ errorDescription: String) -> Void) { + let baseUrl = appDelegate?.urlBase ?? "" // NCBrandOptions.shared.loginBaseUrl + let endPoint = "/ocs/v2.php/apps/files_downloadlimit/\(token)/limit" + let path = baseUrl+endPoint + do { + var urlRequest = try URLRequest(url: URL(string: path)!, method: .get) + urlRequest.addValue("true", forHTTPHeaderField: "OCS-APIREQUEST") + + let sessionConfiguration = URLSessionConfiguration.default + let urlSession = URLSession(configuration: sessionConfiguration) + + let task = urlSession.dataTask(with: urlRequest) { [self] (data, response, error) in + guard error == nil else { + completion(nil, error?.localizedDescription ?? "") + return + } + + if let httpResponse = response as? HTTPURLResponse { + let statusCode = httpResponse.statusCode + print("url: \(String(describing: httpResponse.url))\nStatus Code: \(statusCode)") + if httpResponse.statusCode == 200 { + let parser = XMLParser(data: data!) + parser.delegate = self + parser.parse() + completion(self.downloadLimit, self.message) + } else { + completion(nil, "Invalid Response code: \(statusCode)") + } + } else { + completion(nil, error?.localizedDescription ?? "Invalid Response") + } + } + task.resume() + } catch { + completion(nil, error.localizedDescription) + } + } + + func setDownloadLimit(deleteLimit: Bool, limit: String, token: String, completion: @escaping (_ success: Bool, _ errorDescription: String) -> Void) { + let baseUrl = appDelegate?.urlBase ?? "" //NCBrandOptions.shared.loginBaseUrl + let endPoint = "/ocs/v2.php/apps/files_downloadlimit/\(token)/limit" + let path = baseUrl+endPoint + do { + + let method = deleteLimit ? HTTPMethod.delete : .put + var urlRequest = try URLRequest(url: URL(string: path)!, method: method) + + urlRequest.addValue("true", forHTTPHeaderField: "OCS-APIREQUEST") + urlRequest.addValue(authorizationToken(), forHTTPHeaderField: "Authorization") + urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type") + + let parameters = ["token": token, "limit": limit] + + let encoder = JSONEncoder() + let jsonData = try encoder.encode(parameters) + urlRequest.httpBody = jsonData + + let sessionConfiguration = URLSessionConfiguration.default + let urlSession = URLSession(configuration: sessionConfiguration) + + let task = urlSession.dataTask(with: urlRequest) { (data, response, error) in + guard error == nil else { + completion(false, error?.localizedDescription ?? "") + return + } + + if let httpResponse = response as? HTTPURLResponse { + let statusCode = httpResponse.statusCode + print("url: \(String(describing: httpResponse.url))\nStatus Code: \(statusCode)") + if httpResponse.statusCode == 200 { + completion(true, error?.localizedDescription ?? "") + } else { + completion(false, "Invalid Response code: \(statusCode)") + } + } else { + completion(false, error?.localizedDescription ?? "Invalid Response") + } + } + task.resume() + } catch { + completion(false, error.localizedDescription) + } + } + + public func authorizationToken() -> String { + let accountDetails = NCManageDatabase.shared.getAllAccount().first + let password = NCKeychain().getPassword(account: accountDetails?.account ?? "") + let username = accountDetails?.user ?? "" + let credential = Data("\(username):\(password)".utf8).base64EncodedString() + return ("Basic \(credential)") + } + + + // MARK:- XML Parser Delegate + public func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) { + + } + public func parser(_ parser: XMLParser, foundCharacters string: String) { + self.foundCharacters += string; + } + + public func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { + if elementName == "limit" { + let limit = self.foundCharacters.replacingOccurrences(of: "\n", with: "") + self.downloadLimit.limit = Int(limit.trimmingCharacters(in: .whitespaces)) + } + if elementName == "count" { + let count = self.foundCharacters.replacingOccurrences(of: "\n", with: "") + self.downloadLimit.count = Int(count.trimmingCharacters(in: .whitespaces)) + } + if elementName == "message"{ + self.message = self.foundCharacters + } + self.foundCharacters = "" + } +} + +struct DownloadLimit: Codable { + var limit: Int? + var count: Int? +} diff --git a/iOSClient/Utility/NCUtility.swift b/iOSClient/Utility/NCUtility.swift index 95d9279b37..a53dee4095 100644 --- a/iOSClient/Utility/NCUtility.swift +++ b/iOSClient/Utility/NCUtility.swift @@ -284,4 +284,11 @@ final class NCUtility: NSObject, Sendable { } return height } + + func isValidEmail(_ email: String) -> Bool { + + let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" + let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx) + return emailPred.evaluate(with: email) + } } From f7a997f050c9f0ba015a9ccf810e59a475d67dc0 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Mon, 26 Aug 2024 11:28:46 +0530 Subject: [PATCH 002/177] NMC 1992 - Sharing changes after NC release 5.5.4 --- iOSClient/Activity/NCActivity.swift | 2 +- iOSClient/Menu/NCShare+Menu.swift | 5 +- .../Advanced/NCShareAdvancePermission.swift | 58 ++++++++++--------- iOSClient/Share/NCShare.swift | 8 +-- iOSClient/Share/NCShareCommon.swift | 2 +- iOSClient/Share/NCShareHeader.swift | 8 +-- iOSClient/Share/NCShareLinkCell.swift | 9 +-- iOSClient/Share/NCSharePaging.swift | 6 +- iOSClient/Share/NCShareUserCell.swift | 14 +++-- 9 files changed, 58 insertions(+), 54 deletions(-) diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index d524d1d305..41bbe685de 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -32,7 +32,7 @@ class NCActivity: UIViewController, NCSharePagingContent { @IBOutlet weak var tableView: UITableView! var commentView: NCActivityCommentView? - var textField: UIView? { commentView?.newCommentField } + var textField: UITextField? { commentView?.newCommentField } var height: CGFloat = 0 var metadata: tableMetadata? var showComments: Bool = false diff --git a/iOSClient/Menu/NCShare+Menu.swift b/iOSClient/Menu/NCShare+Menu.swift index 69d689d352..c7008d0dd5 100644 --- a/iOSClient/Menu/NCShare+Menu.swift +++ b/iOSClient/Menu/NCShare+Menu.swift @@ -199,12 +199,11 @@ extension NCShare { actions.append( NCMenuAction( title: NSLocalizedString("_share_file_drop_", comment: ""), - icon: tableShare.permissions == NCGlobal.shared.permissionCreateShare ? UIImage(named: "success")?.image(color: NCBrandColor.shared.customer, size: 25.0) ?? UIImage() : UIImage(), + icon: tableShare.permissions == permissions.permissionCreateShare ? UIImage(named: "success")?.image(color: NCBrandColor.shared.customer, size: 25.0) ?? UIImage() : UIImage(), selected: false, on: false, action: { menuAction in - let permissions = NCGlobal.shared.permissionCreateShare - self.updateSharePermissions(share: tableShare, permissions: permissions) + self.updateSharePermissions(share: tableShare, permissions: permissions.permissionCreateShare) } ) ) diff --git a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift index 31a0d98b53..ec15ee3fa6 100644 --- a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift +++ b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift @@ -25,6 +25,7 @@ import UIKit import NextcloudKit import SVGKit import CloudKit +import XLForm class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDelegate, NCShareNavigationTitleSetting { //class NCShareAdvancePermission: XLFormViewController, NCShareAdvanceFotterDelegate, NCShareDetail { @@ -191,7 +192,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg } func setupHeaderView() { - guard let headerView = (Bundle.main.loadNibNamed("NCShareHeader", owner: self, options: nil)?.first as? NCShareHeader) else { return } + guard let headerView = (Bundle.main.loadNibNamed("NCShareAdvancePermissionHeader", owner: self, options: nil)?.first as? NCShareAdvancePermissionHeader) else { return } headerView.setupUI(with: metadata) headerView.ocId = metadata.ocId headerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 190) @@ -238,7 +239,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg let form : XLFormDescriptor var section : XLFormSectionDescriptor var row : XLFormRowDescriptor - + let permissions = NCPermissions() form = XLFormDescriptor(title: "Other Cells") //Sharing @@ -262,12 +263,12 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg row.cellConfig["titleLabel.text"] = NSLocalizedString("_share_read_only_", comment: "") row.height = 44 - if let permission = self.permission, !CCUtility.isAnyPermission(toEdit: permission), permission != NCGlobal.shared.permissionCreateShare { + if let permission = self.permission, !permissions.isAnyPermissionToEdit(permission), permission != permissions.permissionCreateShare { row.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: NCBrandColor.shared.customer, size: 25.0) } if isNewShare { row.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: NCBrandColor.shared.customer, size: 25.0) - self.permission = NCGlobal.shared.permissionReadShare + self.permission = permissions.permissionReadShare } section.addFormRow(row) @@ -278,7 +279,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg row.cellConfig["titleLabel.text"] = NSLocalizedString("_share_allow_editing_", comment: "") row.height = 44 if let permission = self.permission { - if CCUtility.isAnyPermission(toEdit: permission), permission != NCGlobal.shared.permissionCreateShare { + if permissions.isAnyPermissionToEdit(permission), permission != permissions.permissionCreateShare { row.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: NCBrandColor.shared.customer, size: 25.0) } } @@ -300,7 +301,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFilePermissionCell"] = NCFilePermissionCell.self row = XLFormRowDescriptor(tag: "NCFilePermissionCellFileDrop", rowType: "kNMCFilePermissionCell", title: NSLocalizedString("_PERMISSIONS_", comment: "")) row.cellConfig["titleLabel.text"] = NSLocalizedString("_share_file_drop_", comment: "") - if self.permission == NCGlobal.shared.permissionCreateShare { + if self.permission == permissions.permissionCreateShare { row.cellConfig["imageCheck.image"] = UIImage(named: "success")!.image(color: NCBrandColor.shared.customer, size: 25.0) } row.height = 44 @@ -529,11 +530,11 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg override func didSelectFormRow(_ formRow: XLFormRowDescriptor!) { guard let metadata = self.metadata else { return } - + let permissions = NCPermissions() switch formRow.tag { case "NCFilePermissionCellRead": - let value = CCUtility.getPermissionsValue(byCanEdit: false, andCanCreate: false, andCanChange: false, andCanDelete: false, andCanShare: canReshareTheShare(), andIsFolder: metadata.directory) + let value = permissions.getPermission(canEdit: false, canCreate: false, canChange: false, canDelete: false, canShare: canReshareTheShare(), isDirectory: metadata.directory) self.permission = value // self.permissions = "RDNVCK" metadata.permissions = "RDNVCK" @@ -550,7 +551,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg self.reloadForm() break case "kNMCFilePermissionCellEditing": - let value = CCUtility.getPermissionsValue(byCanEdit: true, andCanCreate: true, andCanChange: true, andCanDelete: true, andCanShare: canReshareTheShare(), andIsFolder: metadata.directory) + let value = permissions.getPermission(canEdit: true, canCreate: true, canChange: true, canDelete: true, canShare: canReshareTheShare(), isDirectory: metadata.directory) self.permission = value // self.permissions = "RGDNV" metadata.permissions = "RGDNV" @@ -566,7 +567,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg self.reloadForm() break case "NCFilePermissionCellFileDrop": - self.permission = NCGlobal.shared.permissionCreateShare + self.permission = permissions.permissionCreateShare // self.permissions = "RGDNVCK" metadata.permissions = "RGDNVCK" if let row : XLFormRowDescriptor = self.form.formRow(withTag: "NCFilePermissionCellRead") { @@ -596,7 +597,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg func canReshareTheShare() -> Bool { if let permissionValue = self.permission { - let canReshare = CCUtility.isPermission(toCanShare: permissionValue) + let canReshare = NCPermissions().isPermissionToCanShare(permissionValue) return canReshare } else { return false @@ -604,6 +605,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg } override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + let permissions = NCPermissions() if let advancePermissionHeaderRow: XLFormRowDescriptor = self.form.formRow(withTag: "NCFilePermissionCellAdvanceTxt") { if let advancePermissionHeaderRowIndexPath = form.indexPath(ofFormRow: advancePermissionHeaderRow), indexPath == advancePermissionHeaderRowIndexPath { let cell = cell as? NCFilePermissionCell @@ -617,7 +619,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg let cell = cell as? NCFilePermissionEditCell // Can reshare (file) if let permissionValue = self.permission { - let canReshare = CCUtility.isPermission(toCanShare: permissionValue) + let canReshare = permissions.isPermissionToCanShare(permissionValue) cell?.switchControl.isOn = canReshare } else { //new share @@ -630,9 +632,9 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg if let hideDownloadRowIndexPath = form.indexPath(ofFormRow: hideDownloadRow), indexPath == hideDownloadRowIndexPath { let cell = cell as? NCFilePermissionEditCell cell?.switchControl.isOn = oldTableShare?.hideDownload ?? false - cell?.titleLabel.isEnabled = !(self.permission == NCGlobal.shared.permissionCreateShare) - cell?.switchControl.isEnabled = !(self.permission == NCGlobal.shared.permissionCreateShare) - cell?.isUserInteractionEnabled = !(self.permission == NCGlobal.shared.permissionCreateShare) + cell?.titleLabel.isEnabled = !(self.permission == permissions.permissionCreateShare) + cell?.switchControl.isEnabled = !(self.permission == permissions.permissionCreateShare) +// cell?.isUserInteractionEnabled = !(self.permission == permissions.permissionCreateShare) } // set password @@ -786,8 +788,8 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg } // EDITORS - let editors = NCUtility().isDirectEditing(account: metadata.account, contentType: metadata.contentType) - let availableRichDocument = NCUtility().isRichDocument(metadata) + let editors = NCUtility().editorsDirectEditing(account: metadata.account, contentType: metadata.contentType) + let availableRichDocument = NCUtility().isTypeFileRichDocument(metadata) // RichDocument: Collabora return (availableRichDocument && editors.count == 0) @@ -826,31 +828,31 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg } func canReshareValueChanged(isOn: Bool) { - + let permissions = NCPermissions() guard let oldTableShare = oldTableShare, let permission = self.permission else { - self.permission = isOn ? (self.permission ?? 0) + NCGlobal.shared.permissionShareShare : ((self.permission ?? 0) - NCGlobal.shared.permissionShareShare) + self.permission = isOn ? (self.permission ?? 0) + permissions.permissionShareShare : ((self.permission ?? 0) - permissions.permissionShareShare) return } - let canEdit = CCUtility.isAnyPermission(toEdit: permission) - let canCreate = CCUtility.isPermission(toCanCreate: permission) - let canChange = CCUtility.isPermission(toCanChange: permission) - let canDelete = CCUtility.isPermission(toCanDelete: permission) + let canEdit = permissions.isAnyPermissionToEdit(permission) + let canCreate = permissions.isPermissionToCanCreate(permission) + let canChange = permissions.isPermissionToCanChange(permission) + let canDelete = permissions.isPermissionToCanDelete(permission) if metadata.directory { - self.permission = CCUtility.getPermissionsValue(byCanEdit: canEdit, andCanCreate: canCreate, andCanChange: canChange, andCanDelete: canDelete, andCanShare: isOn, andIsFolder: metadata.directory) + self.permission = permissions.getPermission(canEdit: canEdit, canCreate: canCreate, canChange: canChange, canDelete: canDelete, canShare: isOn, isDirectory: metadata.directory) } else { if isOn { if canEdit { - self.permission = CCUtility.getPermissionsValue(byCanEdit: true, andCanCreate: true, andCanChange: true, andCanDelete: true, andCanShare: isOn, andIsFolder: metadata.directory) + self.permission = permissions.getPermission(canEdit: true, canCreate: true, canChange: true, canDelete: true, canShare: isOn, isDirectory: metadata.directory) } else { - self.permission = CCUtility.getPermissionsValue(byCanEdit: false, andCanCreate: false, andCanChange: false, andCanDelete: false, andCanShare: isOn, andIsFolder: metadata.directory) + self.permission = permissions.getPermission(canEdit: false, canCreate: false, canChange: false, canDelete: false, canShare: isOn, isDirectory: metadata.directory) } } else { if canEdit { - self.permission = CCUtility.getPermissionsValue(byCanEdit: true, andCanCreate: true, andCanChange: true, andCanDelete: true, andCanShare: isOn, andIsFolder: metadata.directory) + self.permission = permissions.getPermission(canEdit: true, canCreate: true, canChange: true, canDelete: true, canShare: isOn, isDirectory: metadata.directory) } else { - self.permission = CCUtility.getPermissionsValue(byCanEdit: false, andCanCreate: false, andCanChange: false, andCanDelete: false, andCanShare: isOn, andIsFolder: metadata.directory) + self.permission = permissions.getPermission(canEdit: false, canCreate: false, canChange: false, canDelete: false, canShare: isOn, isDirectory: metadata.directory) } } let alertController = UIAlertController.password(titleKey: "_share_password_") { password in diff --git a/iOSClient/Share/NCShare.swift b/iOSClient/Share/NCShare.swift index ef14b0be71..669af869d1 100644 --- a/iOSClient/Share/NCShare.swift +++ b/iOSClient/Share/NCShare.swift @@ -424,8 +424,8 @@ extension NCShare: NCShareNetworkingDelegate { } // EDITORS - let editors = utility.isDirectEditing(account: metadata.account, contentType: metadata.contentType) - let availableRichDocument = utility.isRichDocument(metadata) + let editors = utility.editorsDirectEditing(account: metadata.account, contentType: metadata.contentType) + let availableRichDocument = utility.isTypeFileRichDocument(metadata) // RichDocument: Collabora return (availableRichDocument && editors.count == 0) @@ -593,9 +593,9 @@ extension NCShare: UITableViewDataSource { headerView.fileName.text = metadata?.fileNameView headerView.fileName.textColor = NCBrandColor.shared.label if metadata!.favorite { - headerView.favorite.setImage(utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite, size: 24), for: .normal) + headerView.favorite.setImage(utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.yellowFavorite], size: 24), for: .normal) } else { - headerView.favorite.setImage(utility.loadImage(named: "star.fill", color: NCBrandColor.shared.textInfo, size: 24), for: .normal) + headerView.favorite.setImage(utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.textInfo], size: 24), for: .normal) } headerView.info.text = utilityFileSystem.transformedSize(metadata!.size) + ", " + utility.dateDiff(metadata!.date as Date) return headerView diff --git a/iOSClient/Share/NCShareCommon.swift b/iOSClient/Share/NCShareCommon.swift index 09c8de9495..8a23206c99 100644 --- a/iOSClient/Share/NCShareCommon.swift +++ b/iOSClient/Share/NCShareCommon.swift @@ -143,6 +143,6 @@ class NCShareCommon: NSObject { } func canReshare(withPermission permission: String) -> Bool { - return permission.contains(NCGlobal.shared.permissionCanShare) + return permission.contains(NCPermissions().permissionCanShare) } } diff --git a/iOSClient/Share/NCShareHeader.swift b/iOSClient/Share/NCShareHeader.swift index 749197c7af..5df6fc5c96 100644 --- a/iOSClient/Share/NCShareHeader.swift +++ b/iOSClient/Share/NCShareHeader.swift @@ -56,9 +56,9 @@ class NCShareAdvancePermissionHeader: UIView { fileName.text = metadata.fileNameView fileName.textColor = NCBrandColor.shared.fileFolderName if metadata.favorite { - favorite.setImage(utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite, size: 24), for: .normal) + favorite.setImage(utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.yellowFavorite], size: 24), for: .normal) } else { - favorite.setImage(utility.loadImage(named: "star.fill", color: NCBrandColor.shared.textInfo, size: 24), for: .normal) + favorite.setImage(utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.textInfo], size: 24), for: .normal) } info.textColor = NCBrandColor.shared.optionItem info.text = utilityFileSystem.transformedSize(metadata.size) + ", " + utility.dateDiff(metadata.date as Date) @@ -70,9 +70,9 @@ class NCShareAdvancePermissionHeader: UIView { if error == .success { guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) else { return } if metadata.favorite { - self.favorite.setImage(self.utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite, size: 24), for: .normal) + self.favorite.setImage(self.utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.yellowFavorite], size: 24), for: .normal) } else { - self.favorite.setImage(self.utility.loadImage(named: "star.fill", color: NCBrandColor.shared.textInfo, size: 24), for: .normal) + self.favorite.setImage(self.utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.textInfo], size: 24), for: .normal) } } else { NCContentPresenter().showError(error: error) diff --git a/iOSClient/Share/NCShareLinkCell.swift b/iOSClient/Share/NCShareLinkCell.swift index 6d90546526..de045ff991 100644 --- a/iOSClient/Share/NCShareLinkCell.swift +++ b/iOSClient/Share/NCShareLinkCell.swift @@ -130,25 +130,26 @@ class NCShareLinkCell: UITableViewCell { override func awakeFromNib() { super.awakeFromNib() - + buttonMenu.contentMode = .scaleAspectFill imageItem.image = UIImage(named: "sharebylink")?.image(color: NCBrandColor.shared.label, size: 30) buttonCopy.setImage(UIImage.init(named: "shareCopy")!.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) - buttonMenu.setImage(UIImage.init(named: "shareMenu")!.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) + buttonMenu.setImage(NCImageCache.images.buttonMore.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) labelQuickStatus.textColor = NCBrandColor.shared.customer } func setupCellUI() { + let permissions = NCPermissions() guard let tableShare = tableShare else { return } contentView.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground labelTitle.textColor = NCBrandColor.shared.label - if tableShare.permissions == NCGlobal.shared.permissionCreateShare { + if tableShare.permissions == permissions.permissionCreateShare { labelQuickStatus.text = NSLocalizedString("_share_file_drop_", comment: "") } else { // Read Only - if CCUtility.isAnyPermission(toEdit: tableShare.permissions) { + if permissions.isAnyPermissionToEdit(tableShare.permissions) { labelQuickStatus.text = NSLocalizedString("_share_editing_", comment: "") } else { labelQuickStatus.text = NSLocalizedString("_share_read_only_", comment: "") diff --git a/iOSClient/Share/NCSharePaging.swift b/iOSClient/Share/NCSharePaging.swift index 2ee6b5edd1..0873ffde90 100644 --- a/iOSClient/Share/NCSharePaging.swift +++ b/iOSClient/Share/NCSharePaging.swift @@ -305,7 +305,7 @@ class NCShareHeaderView: UIView { } var canReshare: Bool { guard let metadata = metadata else { return true } - return ((metadata.sharePermissionsCollaborationServices & NCGlobal.shared.permissionShareShare) != 0) + return ((metadata.sharePermissionsCollaborationServices & NCPermissions().permissionShareShare) != 0) } canShareInfoView.isHidden = isCurrentUser labelSharingInfo.isHidden = !isCurrentUser @@ -326,9 +326,9 @@ class NCShareHeaderView: UIView { if error == .success { guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) else { return } if metadata.favorite { - self.favorite.setImage(self.utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite, size: 24), for: .normal) + self.favorite.setImage(self.utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.yellowFavorite], size: 24), for: .normal) } else { - self.favorite.setImage(self.utility.loadImage(named: "star.fill", color: NCBrandColor.shared.textInfo, size: 24), for: .normal) + self.favorite.setImage(self.utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.textInfo], size: 24), for: .normal) } } else { NCContentPresenter().showError(error: error) diff --git a/iOSClient/Share/NCShareUserCell.swift b/iOSClient/Share/NCShareUserCell.swift index ddc9c241b2..42e3e56f8a 100644 --- a/iOSClient/Share/NCShareUserCell.swift +++ b/iOSClient/Share/NCShareUserCell.swift @@ -71,7 +71,7 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { labelTitle.lineBreakMode = .byTruncatingMiddle labelTitle.textColor = NCBrandColor.shared.textColor contentView.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground - + let permissions = NCPermissions() labelTitle.text = tableShare.shareWithDisplayname labelTitle.textColor = NCBrandColor.shared.label isUserInteractionEnabled = true @@ -82,10 +82,10 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { imageItem.image = NCShareCommon().getImageShareType(shareType: tableShare.shareType) let status = utility.getUserStatus(userIcon: tableShare.userIcon, userStatus: tableShare.userStatus, userMessage: tableShare.userMessage) - imageStatus.image = status.onlineStatus + imageStatus.image = status.statusImage self.status.text = status.statusMessage - if CCUtility.isAnyPermission(toEdit: tableShare.permissions) { + if permissions.isAnyPermissionToEdit(tableShare.permissions) { switchCanEdit.setOn(true, animated: false) } else { switchCanEdit.setOn(false, animated: false) @@ -142,11 +142,11 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { default: return "" } - if tableShare.permissions == NCGlobal.shared.permissionCreateShare { + if tableShare.permissions == permissions.permissionCreateShare { labelQuickStatus.text = NSLocalizedString("_share_file_drop_", comment: "") } else { // Read Only - if CCUtility.isAnyPermission(toEdit: tableShare.permissions) { + if permissions.isAnyPermissionToEdit(tableShare.permissions) { labelQuickStatus.text = NSLocalizedString("_share_editing_", comment: "") } else { labelQuickStatus.text = NSLocalizedString("_share_read_only_", comment: "") @@ -159,6 +159,8 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapAvatarImage(_:))) imageItem?.addGestureRecognizer(tapGesture) buttonMenu.setImage(UIImage.init(named: "shareMenu")!.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) + buttonMenu.contentMode = .scaleAspectFill +// buttonMenu.setImage(NCImageCache.images.buttonMore.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) labelQuickStatus.textColor = NCBrandColor.shared.customer imageDownArrow.image = UIImage(named: "downArrow")?.imageColor(NCBrandColor.shared.customer) switchCanEdit.transform = CGAffineTransform(scaleX: 0.75, y: 0.75) @@ -213,7 +215,7 @@ class NCSearchUserDropDownCell: DropDownCell, NCCellProtocol { let utility = NCUtility() imageShareeType.image = NCShareCommon().getImageShareType(shareType: sharee.shareType, isDropDown: true) let status = utility.getUserStatus(userIcon: sharee.userIcon, userStatus: sharee.userStatus, userMessage: sharee.userMessage) - imageStatus.image = status.onlineStatus + imageStatus.image = status.statusImage self.status.text = status.statusMessage if self.status.text?.count ?? 0 > 0 { centerTitle.constant = -5 From 4c72918fd193b216c34456b889ea798bec1c3dd2 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Mon, 27 Nov 2023 17:19:30 +0530 Subject: [PATCH 003/177] NMC 1997 - Sharing customisation unused changes --- iOSClient/Share/NCShare.swift | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/iOSClient/Share/NCShare.swift b/iOSClient/Share/NCShare.swift index 669af869d1..06b17dfd41 100644 --- a/iOSClient/Share/NCShare.swift +++ b/iOSClient/Share/NCShare.swift @@ -222,6 +222,44 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent textField?.layer.borderColor = NCBrandColor.shared.brand.cgColor } + @objc func keyboardWillHide(notification: Notification) { + if view.frame.origin.y != 0 { + self.view.frame.origin.y = 0 + } + textField?.layer.borderColor = NCBrandColor.shared.label.cgColor + } + + @objc func keyboardWillShow(notification: Notification) { + if UIDevice.current.userInterfaceIdiom == .phone { + if (UIScreen.main.bounds.width < 374 || UIDevice.current.orientation.isLandscape) { + if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { + if view.frame.origin.y == 0 { + self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + self.view.frame.origin.y -= keyboardSize.height + } + } + } else if UIScreen.main.bounds.height < 850 { + if view.frame.origin.y == 0 { + self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + self.view.frame.origin.y -= 70 + } + } else { + if view.frame.origin.y == 0 { + self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + self.view.frame.origin.y -= 40 + } + } + } + + if UIDevice.current.userInterfaceIdiom == .pad, UIDevice.current.orientation.isLandscape { + if view.frame.origin.y == 0 { + self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + self.view.frame.origin.y -= 230 + } + } + textField?.layer.borderColor = NCBrandColor.shared.brand.cgColor + } + @objc func keyboardWillHide(notification: Notification) { if view.frame.origin.y != 0 { self.view.frame.origin.y = 0 From 91879d34157164d014241f2e807a00dbad6fc4f1 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 15 Apr 2025 16:09:15 +0530 Subject: [PATCH 004/177] NMC 1997 - Sharing customisation changes --- Nextcloud.xcodeproj/project.pbxproj | 66 +++++++++++++++++++- iOSClient/Activity/NCActivity.swift | 9 ++- iOSClient/Share/NCShare+Helper.swift | 1 + iOSClient/Share/NCShare.swift | 83 +++++++++++-------------- iOSClient/Share/NCShareCommon.swift | 2 +- iOSClient/Share/NCShareHeader.swift | 10 +-- iOSClient/Share/NCShareLinkCell.swift | 2 +- iOSClient/Share/NCShareNetworking.swift | 14 ++++- iOSClient/Share/NCShareUserCell.swift | 1 - iOSClient/Utility/NCUtility.swift | 30 +++++++-- 10 files changed, 150 insertions(+), 68 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index d333cffc0b..f0260956dc 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -91,7 +91,23 @@ 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 */; }; AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; }; - AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* Shareable.swift */; }; + AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */; }; + B5E2E6D42DAE52B500AB2EDD /* SharingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E2E6D32DAE52B500AB2EDD /* SharingTest.swift */; }; + B5E2E6D72DAE571200AB2EDD /* PasswordInputField.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5E2E6D62DAE571200AB2EDD /* PasswordInputField.xib */; }; + B5E2E6D82DAE571200AB2EDD /* PasswordInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E2E6D52DAE571200AB2EDD /* PasswordInputField.swift */; }; + B5E2E6DD2DAE573B00AB2EDD /* NCFilePermissionEditCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5E2E6DC2DAE573B00AB2EDD /* NCFilePermissionEditCell.xib */; }; + B5E2E6DE2DAE573B00AB2EDD /* NCFilePermissionCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5E2E6DA2DAE573B00AB2EDD /* NCFilePermissionCell.xib */; }; + B5E2E6DF2DAE573B00AB2EDD /* NCFilePermissionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E2E6D92DAE573B00AB2EDD /* NCFilePermissionCell.swift */; }; + B5E2E6E02DAE573B00AB2EDD /* NCFilePermissionEditCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E2E6DB2DAE573B00AB2EDD /* NCFilePermissionEditCell.swift */; }; + B5E2E6E22DAE59CD00AB2EDD /* NCShareAdvancePermissionHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5E2E6E12DAE59CD00AB2EDD /* NCShareAdvancePermissionHeader.xib */; }; + B5E2E6E72DAE59F000AB2EDD /* NCShareHeaderCustomCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5E2E6E42DAE59F000AB2EDD /* NCShareHeaderCustomCell.xib */; }; + B5E2E6E82DAE59F000AB2EDD /* NCShareTextInputCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5E2E6E62DAE59F000AB2EDD /* NCShareTextInputCell.xib */; }; + B5E2E6E92DAE59F000AB2EDD /* NCShareTextInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E2E6E52DAE59F000AB2EDD /* NCShareTextInputCell.swift */; }; + B5E2E6EA2DAE59F000AB2EDD /* NCShareHeaderCustomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E2E6E32DAE59F000AB2EDD /* NCShareHeaderCustomCell.swift */; }; + B5E2E6ED2DAE66AF00AB2EDD /* NCShareEmailFieldCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5E2E6EC2DAE66AF00AB2EDD /* NCShareEmailFieldCell.xib */; }; + B5E2E6EE2DAE66AF00AB2EDD /* NCShareEmailFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E2E6EB2DAE66AF00AB2EDD /* NCShareEmailFieldCell.swift */; }; + B5E2E6F02DAE6E3F00AB2EDD /* ShareDownloadLimitNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E2E6EF2DAE6E3F00AB2EDD /* ShareDownloadLimitNetwork.swift */; }; + C04E2F232A17BB4D001BAD85 /* FilesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */; }; D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; F310B1EF2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F310B1EE2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift */; }; @@ -1331,7 +1347,22 @@ 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 = ""; }; AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareCells.swift; sourceTree = ""; }; - AFCE353827E5DE0400FEA6C2 /* Shareable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shareable.swift; sourceTree = ""; }; + AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShare+Helper.swift"; sourceTree = ""; }; + B5E2E6D32DAE52B500AB2EDD /* SharingTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingTest.swift; sourceTree = ""; }; + B5E2E6D52DAE571200AB2EDD /* PasswordInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordInputField.swift; sourceTree = ""; }; + B5E2E6D62DAE571200AB2EDD /* PasswordInputField.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PasswordInputField.xib; sourceTree = ""; }; + B5E2E6D92DAE573B00AB2EDD /* NCFilePermissionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCFilePermissionCell.swift; sourceTree = ""; }; + B5E2E6DA2DAE573B00AB2EDD /* NCFilePermissionCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCFilePermissionCell.xib; sourceTree = ""; }; + B5E2E6DB2DAE573B00AB2EDD /* NCFilePermissionEditCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCFilePermissionEditCell.swift; sourceTree = ""; }; + B5E2E6DC2DAE573B00AB2EDD /* NCFilePermissionEditCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCFilePermissionEditCell.xib; sourceTree = ""; }; + B5E2E6E12DAE59CD00AB2EDD /* NCShareAdvancePermissionHeader.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCShareAdvancePermissionHeader.xib; sourceTree = ""; }; + B5E2E6E32DAE59F000AB2EDD /* NCShareHeaderCustomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareHeaderCustomCell.swift; sourceTree = ""; }; + B5E2E6E42DAE59F000AB2EDD /* NCShareHeaderCustomCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCShareHeaderCustomCell.xib; sourceTree = ""; }; + B5E2E6E52DAE59F000AB2EDD /* NCShareTextInputCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareTextInputCell.swift; sourceTree = ""; }; + B5E2E6E62DAE59F000AB2EDD /* NCShareTextInputCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCShareTextInputCell.xib; sourceTree = ""; }; + B5E2E6EB2DAE66AF00AB2EDD /* NCShareEmailFieldCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareEmailFieldCell.swift; sourceTree = ""; }; + B5E2E6EC2DAE66AF00AB2EDD /* NCShareEmailFieldCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCShareEmailFieldCell.xib; sourceTree = ""; }; + B5E2E6EF2DAE6E3F00AB2EDD /* ShareDownloadLimitNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareDownloadLimitNetwork.swift; sourceTree = ""; }; C0046CDA2A17B98400D87C9D /* NextcloudUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; @@ -2122,6 +2153,8 @@ isa = PBXGroup; children = ( AA52EB452D42AC5A0089C348 /* Placeholder.swift */, + B5E2E6D32DAE52B500AB2EDD /* SharingTest.swift */, + AF8ED1FB2757821000B8DBC4 /* NextcloudUnitTests.swift */, ); path = NextcloudUnitTests; sourceTree = ""; @@ -2149,6 +2182,15 @@ isa = PBXGroup; children = ( AA8D316D2D4123B200FE2775 /* DownloadLimit */, + B5E2E6E32DAE59F000AB2EDD /* NCShareHeaderCustomCell.swift */, + B5E2E6E42DAE59F000AB2EDD /* NCShareHeaderCustomCell.xib */, + B5E2E6E52DAE59F000AB2EDD /* NCShareTextInputCell.swift */, + B5E2E6E62DAE59F000AB2EDD /* NCShareTextInputCell.xib */, + B5E2E6E12DAE59CD00AB2EDD /* NCShareAdvancePermissionHeader.xib */, + B5E2E6D92DAE573B00AB2EDD /* NCFilePermissionCell.swift */, + B5E2E6DA2DAE573B00AB2EDD /* NCFilePermissionCell.xib */, + B5E2E6DB2DAE573B00AB2EDD /* NCFilePermissionEditCell.swift */, + B5E2E6DC2DAE573B00AB2EDD /* NCFilePermissionEditCell.xib */, AF93471627E2361E002537EE /* NCShareAdvancePermission.swift */, AF93471827E2361E002537EE /* NCShareAdvancePermissionFooter.swift */, AF93471427E2361E002537EE /* NCShareAdvancePermissionFooter.xib */, @@ -2379,6 +2421,9 @@ F728CE741BF6322C00E69702 /* Share */ = { isa = PBXGroup; children = ( + B5E2E6EF2DAE6E3F00AB2EDD /* ShareDownloadLimitNetwork.swift */, + B5E2E6EB2DAE66AF00AB2EDD /* NCShareEmailFieldCell.swift */, + B5E2E6EC2DAE66AF00AB2EDD /* NCShareEmailFieldCell.xib */, AF93471327E235EB002537EE /* Advanced */, F724377A2C10B83E00C7C68D /* NCPermissions.swift */, F700510022DF63AC003A3356 /* NCShare.storyboard */, @@ -2505,6 +2550,8 @@ F758B41E212C516300515F55 /* Scan document */ = { isa = PBXGroup; children = ( + B5E2E6D52DAE571200AB2EDD /* PasswordInputField.swift */, + B5E2E6D62DAE571200AB2EDD /* PasswordInputField.xib */, F758B457212C564000515F55 /* NCScan.storyboard */, F758B45D212C569C00515F55 /* NCScanCell.swift */, F758B45F212C56A400515F55 /* NCScan.swift */, @@ -3982,9 +4029,14 @@ F758B45A212C564000515F55 /* NCScan.storyboard in Resources */, F765F73225237E3F00391DBE /* NCRecent.storyboard in Resources */, F78F74342163757000C2ADAD /* NCTrash.storyboard in Resources */, + B5E2E6E72DAE59F000AB2EDD /* NCShareHeaderCustomCell.xib in Resources */, + B5E2E6E82DAE59F000AB2EDD /* NCShareTextInputCell.xib in Resources */, F702F30225EE5D2C008F8E80 /* english.txt in Resources */, F757CC8C29E82D0500F31428 /* NCGroupfolders.storyboard in Resources */, F79A65C32191D90F00FF6DCC /* NCSelect.storyboard in Resources */, + B5E2E6DD2DAE573B00AB2EDD /* NCFilePermissionEditCell.xib in Resources */, + B5E2E6ED2DAE66AF00AB2EDD /* NCShareEmailFieldCell.xib in Resources */, + B5E2E6DE2DAE573B00AB2EDD /* NCFilePermissionCell.xib in Resources */, F7226EDC1EE4089300EBECB1 /* Main.storyboard in Resources */, AF56C1DC2784856200D8BAE2 /* NCActivityCommentView.xib in Resources */, F7F4F10B27ECDBDB008676F9 /* Inconsolata-Light.ttf in Resources */, @@ -4007,9 +4059,11 @@ F723B3DD22FC6D1D00301EFE /* NCShareCommentsCell.xib in Resources */, F78ACD4B21903F850088454D /* NCTrashListCell.xib in Resources */, AF93471927E2361E002537EE /* NCShareAdvancePermissionFooter.xib in Resources */, + B5E2E6E22DAE59CD00AB2EDD /* NCShareAdvancePermissionHeader.xib in Resources */, F7725A61251F33BB00D125E0 /* NCFiles.storyboard in Resources */, F7CBC1232BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */, F700510122DF63AC003A3356 /* NCShare.storyboard in Resources */, + B5E2E6D72DAE571200AB2EDD /* PasswordInputField.xib in Resources */, F787704F22E7019900F287A9 /* NCShareLinkCell.xib in Resources */, F70753F72542A9C000972D44 /* NCViewerMediaPage.storyboard in Resources */, F7F4F10627ECDBDB008676F9 /* Inconsolata-Medium.ttf in Resources */, @@ -4151,6 +4205,7 @@ AA52EB472D42AC9E0089C348 /* Placeholder.swift in Sources */, F372087D2BAB4C0F006B5430 /* TestConstants.swift in Sources */, F78E2D6C29AF02DB0024D4F3 /* Database.swift in Sources */, + B5E2E6D42DAE52B500AB2EDD /* SharingTest.swift in Sources */, F7817CFE29801A3500FFBC65 /* Data+Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4569,6 +4624,7 @@ 370D26AF248A3D7A00121797 /* NCCellProtocol.swift in Sources */, F32FADA92D1176E3007035E2 /* UIButton+Extension.swift in Sources */, F768822C2C0DD1E7001CF441 /* NCKeychain.swift in Sources */, + B5E2E6EE2DAE66AF00AB2EDD /* NCShareEmailFieldCell.swift in Sources */, F7BFFD282C8846020029A201 /* NCHud.swift in Sources */, F71CD6CA2930D7B1006C95C1 /* NCApplicationHandle.swift in Sources */, F3754A7D2CF87D600009312E /* SetupPasscodeView.swift in Sources */, @@ -4576,6 +4632,8 @@ F3374A842D64AC31002A38F9 /* AssistantLabelStyle.swift in Sources */, F74BAE172C7E2F4E0028D4FA /* FileProviderDomain.swift in Sources */, F76882402C0DD30B001CF441 /* ViewOnAppear.swift in Sources */, + B5E2E6E92DAE59F000AB2EDD /* NCShareTextInputCell.swift in Sources */, + B5E2E6EA2DAE59F000AB2EDD /* NCShareHeaderCustomCell.swift in Sources */, F790110E21415BF600D7B136 /* NCViewerRichDocument.swift in Sources */, F78ACD4021903CC20088454D /* NCGridCell.swift in Sources */, F7D890752BD25C570050B8A6 /* NCCollectionViewCommon+DragDrop.swift in Sources */, @@ -4710,6 +4768,7 @@ F73EF7BF2B02250B0087E6E9 /* NCManageDatabase+GPS.swift in Sources */, F39A1EE22D0AF8A400DAD522 /* Albums.swift in Sources */, F71F6D072B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */, + B5E2E6D82DAE571200AB2EDD /* PasswordInputField.swift in Sources */, F761856C29E98543006EB3B0 /* NCIntroCollectionViewCell.swift in Sources */, F75DD765290ABB25002EB562 /* Intent.intentdefinition in Sources */, F7D4BF012CA1831900A5E746 /* NCCollectionViewCommonPinchGesture.swift in Sources */, @@ -4770,6 +4829,7 @@ F702F30125EE5D2C008F8E80 /* NYMnemonic.m in Sources */, AF93474E27E3F212002537EE /* NCShareNewUserAddComment.swift in Sources */, F7C30DFD291BD0B80017149B /* NCNetworkingE2EEDelete.swift in Sources */, + B5E2E6F02DAE6E3F00AB2EDD /* ShareDownloadLimitNetwork.swift in Sources */, F76882302C0DD1E7001CF441 /* NCFileNameModel.swift in Sources */, F7CF06882E1127460063AD04 /* NCManageDatabase+Metadata+Create.swift in Sources */, F72FD3B5297ED49A00075D28 /* NCManageDatabase+E2EE.swift in Sources */, @@ -4799,6 +4859,8 @@ AF93471B27E2361E002537EE /* NCShareAdvancePermission.swift in Sources */, F77BC3ED293E528A005F2B08 /* NCConfigServer.swift in Sources */, F7A560422AE1593700BE8FD6 /* NCOperationSaveLivePhoto.swift in Sources */, + B5E2E6DF2DAE573B00AB2EDD /* NCFilePermissionCell.swift in Sources */, + B5E2E6E02DAE573B00AB2EDD /* NCFilePermissionEditCell.swift in Sources */, F7D1C4AC2C9484FD00EC6D44 /* NCMedia+CollectionViewDataSourcePrefetching.swift in Sources */, F7D368DF2DAFE19E0037E7C6 /* NCActivityNavigationController.swift in Sources */, F7A03E332D426115007AA677 /* NCMoreNavigationController.swift in Sources */, diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index 41bbe685de..e7cc0b9759 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -54,6 +54,7 @@ class NCActivity: UIViewController, NCSharePagingContent { didSet { tableView.tableFooterView?.isHidden = hasActivityToLoad } } var dateAutomaticFetch: Date? + private let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! var session: NCSession.Session { if account.isEmpty { @@ -103,6 +104,8 @@ class NCActivity: UIViewController, NCSharePagingContent { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + appDelegate.activeViewController = self + navigationController?.setNavigationBarAppearance() fetchAll(isInitial: true) } @@ -130,14 +133,10 @@ class NCActivity: UIViewController, NCSharePagingContent { let label = UILabel() label.font = UIFont.systemFont(ofSize: 15) - label.textColor = NCBrandColor.shared.textColor2 + label.textColor = UIColor.systemGray label.textAlignment = .center label.text = NSLocalizedString("_no_activity_footer_", comment: "") view.addSubview(label) - label.translatesAutoresizingMaskIntoConstraints = false - label.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true - label.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true - label.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true return view } diff --git a/iOSClient/Share/NCShare+Helper.swift b/iOSClient/Share/NCShare+Helper.swift index b08bfa9358..6f98012b78 100644 --- a/iOSClient/Share/NCShare+Helper.swift +++ b/iOSClient/Share/NCShare+Helper.swift @@ -92,6 +92,7 @@ class NCTableShareOptions: NCTableShareable { convenience init(sharee: NKSharee, metadata: tableMetadata, password: String?) { self.init(shareType: sharee.shareType, metadata: metadata, password: password) self.shareWith = sharee.shareWith + self.shareWithDisplayname = sharee.label } static func shareLink(metadata: tableMetadata, password: String?) -> NCTableShareOptions { diff --git a/iOSClient/Share/NCShare.swift b/iOSClient/Share/NCShare.swift index 06b17dfd41..5655d3d956 100644 --- a/iOSClient/Share/NCShare.swift +++ b/iOSClient/Share/NCShare.swift @@ -190,43 +190,10 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent @objc func openShareProfile(_ sender: UITapGestureRecognizer) { self.showProfileMenu(userId: metadata.ownerId, session: session, sender: sender.view) } - - @objc func keyboardWillShow(notification: Notification) { - if UIDevice.current.userInterfaceIdiom == .phone { - if (UIScreen.main.bounds.width < 374 || UIDevice.current.orientation.isLandscape) { - if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { - if view.frame.origin.y == 0 { - self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) - self.view.frame.origin.y -= keyboardSize.height - } - } - } else if UIScreen.main.bounds.height < 850 { - if view.frame.origin.y == 0 { - self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) - self.view.frame.origin.y -= 70 - } - } else { - if view.frame.origin.y == 0 { - self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) - self.view.frame.origin.y -= 40 - } - } - } - - if UIDevice.current.userInterfaceIdiom == .pad, UIDevice.current.orientation.isLandscape { - if view.frame.origin.y == 0 { - self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) - self.view.frame.origin.y -= 230 - } - } - textField?.layer.borderColor = NCBrandColor.shared.brand.cgColor - } - - @objc func keyboardWillHide(notification: Notification) { - if view.frame.origin.y != 0 { - self.view.frame.origin.y = 0 - } - textField?.layer.borderColor = NCBrandColor.shared.label.cgColor + + @objc func openShareProfile() { + guard let metadata = metadata else { return } + self.showProfileMenu(userId: metadata.ownerId, session: session) } @objc func keyboardWillShow(notification: Notification) { @@ -275,6 +242,9 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent @objc func reloadData() { shares = self.database.getTableShares(metadata: metadata) shareLinksCount = 0 +// if let metadata = metadata { +// shares = self.database.getTableShares(metadata: metadata) +// } tableView.reloadData() } @@ -287,14 +257,29 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent } @IBAction func searchFieldDidChange(textField: UITextField) { + NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(searchSharees), object: nil) guard let searchString = textField.text else {return} if searchString.count == 0 { dropDown.hide() } else { - networking?.getSharees(searchString: searchString) +// networking?.getSharees(searchString: searchString) + perform(#selector(searchSharees), with: nil, afterDelay: 0.5) } } + @objc private func searchSharees() { + // https://stackoverflow.com/questions/25471114/how-to-validate-an-e-mail-address-in-swift + func isValidEmail(_ email: String) -> Bool { + + let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" + let emailPred = NSPredicate(format: "SELF MATCHES %@", emailRegEx) + return emailPred.evaluate(with: email) + } + guard let searchString = textField?.text, !searchString.isEmpty else { return } + if searchString.contains("@"), !isValidEmail(searchString) { return } + networking?.getSharees(searchString: searchString) + } + @IBAction func createLinkClicked(_ sender: Any) { appDelegate?.adjust.trackEvent(TriggerEvent(CreateLink.rawValue)) TealiumHelper.shared.trackEvent(title: "magentacloud-app.sharing.create", data: ["": ""]) @@ -304,7 +289,7 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent @IBAction func touchUpInsideButtonMenu(_ sender: Any) { guard let metadata = metadata else { return } - let isFilesSharingPublicPasswordEnforced = NCGlobal.shared.capabilityFileSharingPubPasswdEnforced + let isFilesSharingPublicPasswordEnforced = NCCapabilities.Capabilities().capabilityFileSharingPubPasswdEnforced let shares = NCManageDatabase.shared.getTableShares(metadata: metadata) if isFilesSharingPublicPasswordEnforced && shares.firstShareLink == nil { @@ -565,6 +550,7 @@ extension NCShare: UITableViewDataSource { } else { cell.labelTitle.text = directory ? NSLocalizedString("_share_link_folder_", comment: "") : NSLocalizedString("_share_link_file_", comment: "") } +// cell.setupCellUI(userId: session.userId) let isEditingAllowed = shareCommon.isEditingEnabled(isDirectory: directory, fileExtension: metadata?.fileExtension ?? "", shareType: tableShare.shareType) if isEditingAllowed || directory || checkIsCollaboraFile() { cell.btnQuickStatus.isEnabled = true @@ -613,18 +599,23 @@ extension NCShare: UITableViewDataSource { headerView.updateCanReshareUI() - if FileManager.default.fileExists(atPath: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata?.ocId ?? "", etag: metadata?.etag ?? "")) { - headerView.fullWidthImageView.image = UIImage(contentsOfFile: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata?.ocId ?? "", etag: metadata?.etag ?? "")) + if let image = NCUtility().getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt1024) { + headerView.fullWidthImageView.image = image +// headerView.fullWidthImageView.image = UIImage(contentsOfFile: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata?.ocId ?? "", etag: metadata?.etag ?? "")) headerView.fullWidthImageView.contentMode = .scaleAspectFill headerView.imageView.isHidden = true } else { if metadata?.directory ?? false { - let image = (metadata?.e2eEncrypted ?? false) ? UIImage(named: "folderEncrypted") : UIImage(named: "folder_nmcloud") + let image = (metadata?.e2eEncrypted ?? false) ? NCImageCache.shared.getFolderEncrypted() : NCImageCache.shared.getFolder() headerView.imageView.image = image } else if !(metadata?.iconName.isEmpty ?? false) { - headerView.imageView.image = metadata!.fileExtension == "odg" ? UIImage(named: "file-diagram") : UIImage.init(named: metadata!.iconName) + if let image = UIImage.init(named: metadata!.iconName) { + headerView.imageView.image = metadata!.fileExtension == "odg" ? UIImage(named: "diagram") : image + } else { + headerView.imageView.image = metadata!.fileExtension == "odg" ? UIImage(named: "diagram") : NCUtility().loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) + } } else { - headerView.imageView.image = UIImage(named: "file") + headerView.imageView.image = NCImageCache.shared.getImageFile() } } @@ -640,10 +631,10 @@ extension NCShare: UITableViewDataSource { } func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat { - return metadata?.ownerId != appDelegate?.userId ? canReshare ? 400 : 350 : 320 + return metadata?.ownerId != session?.userId ? canReshare ? 400 : 350 : 320 } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return metadata?.ownerId != appDelegate?.userId ? canReshare ? UITableView.automaticDimension : 350 : 320 + return metadata?.ownerId != session?.userId ? canReshare ? UITableView.automaticDimension : 350 : 320 } } diff --git a/iOSClient/Share/NCShareCommon.swift b/iOSClient/Share/NCShareCommon.swift index 8a23206c99..c37c4744c4 100644 --- a/iOSClient/Share/NCShareCommon.swift +++ b/iOSClient/Share/NCShareCommon.swift @@ -136,7 +136,7 @@ class NCShareCommon: NSObject { } func isCurrentUserIsFileOwner(fileOwnerId: String) -> Bool { - if let currentUser = NCManageDatabase.shared.getActiveAccount(), currentUser.userId == fileOwnerId { + if let currentUser = NCManageDatabase.shared.getActiveTableAccount(), currentUser.userId == fileOwnerId { return true } return false diff --git a/iOSClient/Share/NCShareHeader.swift b/iOSClient/Share/NCShareHeader.swift index 5df6fc5c96..862c8f7008 100644 --- a/iOSClient/Share/NCShareHeader.swift +++ b/iOSClient/Share/NCShareHeader.swift @@ -38,17 +38,17 @@ class NCShareAdvancePermissionHeader: UIView { fileName.textColor = NCBrandColor.shared.label info.textColor = NCBrandColor.shared.textInfo backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground - if FileManager.default.fileExists(atPath: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)) { - fullWidthImageView.image = utility.getImageMetadata(metadata, for: frame.height) + if let image = NCUtility().getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt1024) { + fullWidthImageView.image = image fullWidthImageView.contentMode = .scaleAspectFill imageView.isHidden = true } else { if metadata.directory { - imageView.image = UIImage.init(named: "folder") + imageView.image = metadata.e2eEncrypted ? NCImageCache.shared.getFolderEncrypted() : NCImageCache.shared.getFolder() } else if !metadata.iconName.isEmpty { - imageView.image = UIImage.init(named: metadata.iconName) + imageView.image = NCUtility().loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) } else { - imageView.image = UIImage.init(named: "file") + imageView.image = NCImageCache.shared.getImageFile() } } favorite.setNeedsUpdateConstraints() diff --git a/iOSClient/Share/NCShareLinkCell.swift b/iOSClient/Share/NCShareLinkCell.swift index de045ff991..aac988f876 100644 --- a/iOSClient/Share/NCShareLinkCell.swift +++ b/iOSClient/Share/NCShareLinkCell.swift @@ -133,7 +133,7 @@ class NCShareLinkCell: UITableViewCell { buttonMenu.contentMode = .scaleAspectFill imageItem.image = UIImage(named: "sharebylink")?.image(color: NCBrandColor.shared.label, size: 30) buttonCopy.setImage(UIImage.init(named: "shareCopy")!.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) - buttonMenu.setImage(NCImageCache.images.buttonMore.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) + buttonMenu.setImage(UIImage.init(named: "shareMenu")!.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) labelQuickStatus.textColor = NCBrandColor.shared.customer } diff --git a/iOSClient/Share/NCShareNetworking.swift b/iOSClient/Share/NCShareNetworking.swift index 0e9067d6a4..049e9e1b06 100644 --- a/iOSClient/Share/NCShareNetworking.swift +++ b/iOSClient/Share/NCShareNetworking.swift @@ -103,12 +103,15 @@ class NCShareNetworking: NSObject { func createShareLink(password: String?) { NCActivityIndicator.shared.start(backgroundView: view) - let filenamePath = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId) - NextcloudKit.shared.createShareLink(path: filenamePath) { [self] account, share, data, error in + let filenamePath = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, session: session) + NextcloudKit.shared.createShareLink(path: filenamePath, account: metadata.account) { [self] account, share, data, error in NCActivityIndicator.shared.stop() if error == .success && share != nil { - let home = utilityFileSystem.getHomeServer(urlBase: self.metadata.urlBase, userId: self.metadata.userId) + let home = self.utilityFileSystem.getHomeServer(session: self.session) NCManageDatabase.shared.addShare(account: self.metadata.account, home:home, shares: [share!]) + if !metadata.contentType.contains("directory") { + AnalyticsHelper.shared.trackEventWithMetadata(eventName: .EVENT__SHARE_FILE ,metadata: metadata) + } } else if error != .success{ NCContentPresenter().showError(error: error) } @@ -154,6 +157,11 @@ class NCShareNetworking: NSObject { NCNetworking.shared.notifyAllDelegates { delegate in delegate.transferRequestData(serverUrl: self.metadata.serverUrl) } + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterUpdateShare, userInfo: ["account": self.metadata.account, "serverUrl": self.metadata.serverUrl]) + + if !self.metadata.contentType.contains("directory") { + AnalyticsHelper.shared.trackEventWithMetadata(eventName: .EVENT__SHARE_FILE ,metadata: self.metadata) + } } else { NCContentPresenter().showError(error: error) } diff --git a/iOSClient/Share/NCShareUserCell.swift b/iOSClient/Share/NCShareUserCell.swift index 42e3e56f8a..53f501ef2d 100644 --- a/iOSClient/Share/NCShareUserCell.swift +++ b/iOSClient/Share/NCShareUserCell.swift @@ -160,7 +160,6 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { imageItem?.addGestureRecognizer(tapGesture) buttonMenu.setImage(UIImage.init(named: "shareMenu")!.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) buttonMenu.contentMode = .scaleAspectFill -// buttonMenu.setImage(NCImageCache.images.buttonMore.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) labelQuickStatus.textColor = NCBrandColor.shared.customer imageDownArrow.image = UIImage(named: "downArrow")?.imageColor(NCBrandColor.shared.customer) switchCanEdit.transform = CGAffineTransform(scaleX: 0.75, y: 0.75) diff --git a/iOSClient/Utility/NCUtility.swift b/iOSClient/Utility/NCUtility.swift index a53dee4095..eb06877026 100644 --- a/iOSClient/Utility/NCUtility.swift +++ b/iOSClient/Utility/NCUtility.swift @@ -32,6 +32,20 @@ final class NCUtility: NSObject, Sendable { let utilityFileSystem = NCUtilityFileSystem() let global = NCGlobal.shared + @objc func isSimulatorOrTestFlight() -> Bool { + guard let path = Bundle.main.appStoreReceiptURL?.path else { + return false + } + return path.contains("CoreSimulator") || path.contains("sandboxReceipt") + } + + func isSimulator() -> Bool { + guard let path = Bundle.main.appStoreReceiptURL?.path else { + return false + } + return path.contains("CoreSimulator") + } + func isTypeFileRichDocument(_ metadata: tableMetadata) -> Bool { guard metadata.fileNameView != "." else { return false } let fileExtension = (metadata.fileNameView as NSString).pathExtension @@ -116,11 +130,11 @@ final class NCUtility: NSObject, Sendable { } } - func isQuickLookDisplayable(metadata: tableMetadata) -> Bool { + @objc func isQuickLookDisplayable(metadata: tableMetadata) -> Bool { return true } - func ocIdToFileId(ocId: String?) -> String? { + @objc func ocIdToFileId(ocId: String?) -> String? { guard let ocId = ocId else { return nil } let items = ocId.components(separatedBy: "oc") @@ -275,12 +289,20 @@ final class NCUtility: NSObject, Sendable { return (usedmegabytes, totalmegabytes) } - func getHeightHeaderEmptyData(view: UIView, portraitOffset: CGFloat, landscapeOffset: CGFloat) -> CGFloat { +// func removeForbiddenCharacters(_ fileName: String) -> String { +// var fileName = fileName +// for character in global.forbiddenCharacters { +// fileName = fileName.replacingOccurrences(of: character, with: "") +// } +// return fileName +// } + + func getHeightHeaderEmptyData(view: UIView, portraitOffset: CGFloat, landscapeOffset: CGFloat, isHeaderMenuTransferViewEnabled: Bool = false) -> CGFloat { var height: CGFloat = 0 if UIDevice.current.orientation.isPortrait { height = (view.frame.height / 2) - (view.safeAreaInsets.top / 2) + portraitOffset } else { - height = (view.frame.height / 2) + landscapeOffset + height = (view.frame.height / 2) + landscapeOffset + CGFloat(isHeaderMenuTransferViewEnabled ? 35 : 0) } return height } From 409a944e84fd7a993edf93557cf1b8e74ce3cb5a Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 6 Oct 2025 13:01:51 +0530 Subject: [PATCH 005/177] NMC 1997 - Sharing customisation changes and new-sharing-design customisation changes --- Nextcloud.xcodeproj/project.pbxproj | 37 +- iOSClient/Activity/NCActivity.swift | 160 ++-- .../Extensions/DateFormatter+Extension.swift | 13 +- .../Extensions/UIToolbar+Extension.swift | 2 +- iOSClient/Extensions/UIView+Extension.swift | 20 +- iOSClient/Menu/NCShare+Menu.swift | 137 +-- iOSClient/NCGlobal.swift | 326 ++++--- .../DownloadLimitViewModel.swift | 16 + ...hareDownloadLimitTableViewController.swift | 13 +- .../NCShareDownloadLimitViewController.swift | 42 +- .../Advanced/NCShareAdvancePermission.swift | 494 +++++++---- .../NCShareAdvancePermissionFooter.swift | 15 +- .../NCShareAdvancePermissionHeader.xib | 22 +- iOSClient/Share/Advanced/NCShareCells.swift | 196 ++-- .../Share/Advanced/NCShareDateCell.swift | 55 +- .../Advanced/NCShareNewUserAddComment.swift | 61 +- .../Share/Advanced/NCShareTextInputCell.swift | 117 +-- iOSClient/Share/CreateLinkFooterView.swift | 79 ++ iOSClient/Share/NCPermissions.swift | 103 +++ iOSClient/Share/NCSearchUserDropDownCell.xib | 8 +- iOSClient/Share/NCShare+NCCellDelegate.swift | 18 +- iOSClient/Share/NCShare.storyboard | 82 +- iOSClient/Share/NCShare.swift | 835 +++++++++++------- iOSClient/Share/NCShareCommentsCell.swift | 3 +- iOSClient/Share/NCShareCommon.swift | 41 +- iOSClient/Share/NCShareEmailFieldCell.swift | 178 +++- iOSClient/Share/NCShareEmailFieldCell.xib | 217 +++-- .../Share/NCShareEmailLinkHeaderView.swift | 43 + iOSClient/Share/NCShareHeader.swift | 84 +- iOSClient/Share/NCShareHeader.xib | 231 +++-- iOSClient/Share/NCShareLinkCell.swift | 189 ++-- iOSClient/Share/NCShareLinkCell.xib | 233 ++--- .../Share/NCShareNavigationTitleSetting.swift | 2 +- iOSClient/Share/NCShareNetworking.swift | 283 +++--- iOSClient/Share/NCSharePaging.swift | 40 +- iOSClient/Share/NCShareUserCell.swift | 285 +++--- iOSClient/Share/NoSharesFooterView.swift | 63 ++ .../Share/ShareDownloadLimitNetwork.swift | 20 +- iOSClient/Share/Shareable.swift | 44 +- iOSClient/Share/TransientShare.swift | 15 +- iOSClient/Utility/NCUtility.swift | 151 +++- 41 files changed, 2905 insertions(+), 2068 deletions(-) create mode 100644 iOSClient/Share/CreateLinkFooterView.swift create mode 100644 iOSClient/Share/NCPermissions.swift create mode 100644 iOSClient/Share/NCShareEmailLinkHeaderView.swift create mode 100644 iOSClient/Share/NoSharesFooterView.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 883b65ccb4..b504b4b05b 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -91,6 +91,15 @@ AFCE353527E4ED5900FEA6C2 /* DateFormatter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */; }; AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; }; AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */; }; + B5BEBD192E93A5260002C9E5 /* Shareable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BEBD182E93A5260002C9E5 /* Shareable.swift */; }; + B5BEBD1E2E93A74C0002C9E5 /* CreateLinkFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BEBD1A2E93A74C0002C9E5 /* CreateLinkFooterView.swift */; }; + B5BEBD1F2E93A74C0002C9E5 /* NCPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BEBD1B2E93A74C0002C9E5 /* NCPermissions.swift */; }; + B5BEBD202E93A74C0002C9E5 /* NCShareEmailLinkHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BEBD1C2E93A74C0002C9E5 /* NCShareEmailLinkHeaderView.swift */; }; + B5BEBD212E93A74C0002C9E5 /* NoSharesFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BEBD1D2E93A74C0002C9E5 /* NoSharesFooterView.swift */; }; + B5BEBD222E93A74C0002C9E5 /* CreateLinkFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BEBD1A2E93A74C0002C9E5 /* CreateLinkFooterView.swift */; }; + B5BEBD232E93A74C0002C9E5 /* NCPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BEBD1B2E93A74C0002C9E5 /* NCPermissions.swift */; }; + B5BEBD242E93A74C0002C9E5 /* NCShareEmailLinkHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BEBD1C2E93A74C0002C9E5 /* NCShareEmailLinkHeaderView.swift */; }; + B5BEBD252E93A74C0002C9E5 /* NoSharesFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BEBD1D2E93A74C0002C9E5 /* NoSharesFooterView.swift */; }; B5E2E6D42DAE52B500AB2EDD /* SharingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E2E6D32DAE52B500AB2EDD /* SharingTest.swift */; }; B5E2E6D72DAE571200AB2EDD /* PasswordInputField.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5E2E6D62DAE571200AB2EDD /* PasswordInputField.xib */; }; B5E2E6D82DAE571200AB2EDD /* PasswordInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E2E6D52DAE571200AB2EDD /* PasswordInputField.swift */; }; @@ -106,7 +115,6 @@ B5E2E6ED2DAE66AF00AB2EDD /* NCShareEmailFieldCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5E2E6EC2DAE66AF00AB2EDD /* NCShareEmailFieldCell.xib */; }; B5E2E6EE2DAE66AF00AB2EDD /* NCShareEmailFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E2E6EB2DAE66AF00AB2EDD /* NCShareEmailFieldCell.swift */; }; B5E2E6F02DAE6E3F00AB2EDD /* ShareDownloadLimitNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E2E6EF2DAE6E3F00AB2EDD /* ShareDownloadLimitNetwork.swift */; }; - C04E2F232A17BB4D001BAD85 /* FilesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */; }; D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; F310B1EF2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F310B1EE2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift */; }; @@ -692,7 +700,6 @@ F77E8C202E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77E8C1E2E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift */; }; F77E8C212E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77E8C1E2E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift */; }; F77E8C222E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77E8C1E2E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift */; }; - F77E8C232E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77E8C1E2E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift */; }; F77E8C242E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77E8C1E2E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift */; }; F77E8C252E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77E8C1E2E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift */; }; F77ED59128C9CE9D00E24ED0 /* ToolbarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77ED59028C9CE9D00E24ED0 /* ToolbarData.swift */; }; @@ -1381,6 +1388,11 @@ AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extension.swift"; sourceTree = ""; }; AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareCells.swift; sourceTree = ""; }; AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShare+Helper.swift"; sourceTree = ""; }; + B5BEBD182E93A5260002C9E5 /* Shareable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shareable.swift; sourceTree = ""; }; + B5BEBD1A2E93A74C0002C9E5 /* CreateLinkFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateLinkFooterView.swift; sourceTree = ""; }; + B5BEBD1B2E93A74C0002C9E5 /* NCPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCPermissions.swift; sourceTree = ""; }; + B5BEBD1C2E93A74C0002C9E5 /* NCShareEmailLinkHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareEmailLinkHeaderView.swift; sourceTree = ""; }; + B5BEBD1D2E93A74C0002C9E5 /* NoSharesFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoSharesFooterView.swift; sourceTree = ""; }; B5E2E6D32DAE52B500AB2EDD /* SharingTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingTest.swift; sourceTree = ""; }; B5E2E6D52DAE571200AB2EDD /* PasswordInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordInputField.swift; sourceTree = ""; }; B5E2E6D62DAE571200AB2EDD /* PasswordInputField.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PasswordInputField.xib; sourceTree = ""; }; @@ -2200,7 +2212,6 @@ children = ( AA52EB452D42AC5A0089C348 /* Placeholder.swift */, B5E2E6D32DAE52B500AB2EDD /* SharingTest.swift */, - AF8ED1FB2757821000B8DBC4 /* NextcloudUnitTests.swift */, ); path = NextcloudUnitTests; sourceTree = ""; @@ -2467,6 +2478,11 @@ F728CE741BF6322C00E69702 /* Share */ = { isa = PBXGroup; children = ( + B5BEBD1A2E93A74C0002C9E5 /* CreateLinkFooterView.swift */, + B5BEBD1B2E93A74C0002C9E5 /* NCPermissions.swift */, + B5BEBD1C2E93A74C0002C9E5 /* NCShareEmailLinkHeaderView.swift */, + B5BEBD1D2E93A74C0002C9E5 /* NoSharesFooterView.swift */, + B5BEBD182E93A5260002C9E5 /* Shareable.swift */, B5E2E6EF2DAE6E3F00AB2EDD /* ShareDownloadLimitNetwork.swift */, B5E2E6EB2DAE66AF00AB2EDD /* NCShareEmailFieldCell.swift */, B5E2E6EC2DAE66AF00AB2EDD /* NCShareEmailFieldCell.xib */, @@ -2491,7 +2507,7 @@ AF2D7C7D2742559100ADF566 /* NCShareUserCell.swift */, AF93471727E2361E002537EE /* NCShareHeader.xib */, AF93471527E2361E002537EE /* NCShareHeader.swift */, - AFCE353827E5DE0400FEA6C2 /* Shareable.swift */, + AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */, AA8E03D92D2ED83300E7E89C /* TransientShare.swift */, ); path = Share; @@ -4511,6 +4527,10 @@ F76B3CCF1EAE01BD00921AC9 /* NCBrand.swift in Sources */, F72944F32A84246400246839 /* NCEndToEndMetadataV20.swift in Sources */, F7BAADCC1ED5A87C00B7EAD4 /* NCManageDatabase.swift in Sources */, + B5BEBD1E2E93A74C0002C9E5 /* CreateLinkFooterView.swift in Sources */, + B5BEBD1F2E93A74C0002C9E5 /* NCPermissions.swift in Sources */, + B5BEBD202E93A74C0002C9E5 /* NCShareEmailLinkHeaderView.swift in Sources */, + B5BEBD212E93A74C0002C9E5 /* NoSharesFooterView.swift in Sources */, F7327E322B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4715,7 +4735,7 @@ F733598125C1C188002ABA72 /* NCAskAuthorization.swift in Sources */, 370D26AF248A3D7A00121797 /* NCCellProtocol.swift in Sources */, F32FADA92D1176E3007035E2 /* UIButton+Extension.swift in Sources */, - F768822C2C0DD1E7001CF441 /* NCKeychain.swift in Sources */, + F768822C2C0DD1E7001CF441 /* NCPreferences.swift in Sources */, B5E2E6EE2DAE66AF00AB2EDD /* NCShareEmailFieldCell.swift in Sources */, F7BFFD282C8846020029A201 /* NCHud.swift in Sources */, F71CD6CA2930D7B1006C95C1 /* NCApplicationHandle.swift in Sources */, @@ -4748,6 +4768,7 @@ F76D3CF12428B40E005DFA87 /* NCViewerPDFSearch.swift in Sources */, F7245924289BB50C00474787 /* ThreadSafeDictionary.swift in Sources */, F73F537F1E929C8500F8678D /* NCMore.swift in Sources */, + B5BEBD192E93A5260002C9E5 /* Shareable.swift in Sources */, F702F2CF25EE5B5C008F8E80 /* NCGlobal.swift in Sources */, F794E13F2BBC0F70003693D7 /* SceneDelegate.swift in Sources */, F7A3DB932DDE23B5008F7EC8 /* NCDebouncer.swift in Sources */, @@ -4803,7 +4824,7 @@ F704B5E92430C0B800632F5F /* NCCreateFormUploadConflictCell.swift in Sources */, F7327E3D2B73B92800A462C7 /* NCNetworking+Synchronization.swift in Sources */, F72D404923D2082500A97FD0 /* NCViewerNextcloudText.swift in Sources */, - AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */, + AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */, F77BB746289984CA0090FC19 /* UIViewController+Extension.swift in Sources */, F700510522DF6A89003A3356 /* NCShare.swift in Sources */, F72D1007210B6882009C96B7 /* NCPushNotificationEncryption.m in Sources */, @@ -4912,6 +4933,10 @@ F76B3CCE1EAE01BD00921AC9 /* NCBrand.swift in Sources */, F7FA7FFC2C0F4EE40072FC60 /* NCViewerQuickLookView.swift in Sources */, F3A0479B2BD2668800658E7B /* NCAssistant.swift in Sources */, + B5BEBD222E93A74C0002C9E5 /* CreateLinkFooterView.swift in Sources */, + B5BEBD232E93A74C0002C9E5 /* NCPermissions.swift in Sources */, + B5BEBD242E93A74C0002C9E5 /* NCShareEmailLinkHeaderView.swift in Sources */, + B5BEBD252E93A74C0002C9E5 /* NoSharesFooterView.swift in Sources */, F359D8672A7D03420023F405 /* NCUtility+Exif.swift in Sources */, F7A03E352D427312007AA677 /* NCMainNavigationController.swift in Sources */, F769CA192966EA3C00039397 /* ComponentView.swift in Sources */, diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index 5c6a75aba2..77b26d1175 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -1,6 +1,26 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2019 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCActivity.swift +// Nextcloud +// +// Created by Marino Faggiana on 17/01/2019. +// Copyright © 2019 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// Author Henrik Storch +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// import UIKit import SwiftRichString @@ -22,6 +42,7 @@ class NCActivity: UIViewController, NCSharePagingContent { let database = NCManageDatabase.shared var allItems: [DateCompareable] = [] var sectionDates: [Date] = [] + var dataSourceTask: URLSessionTask? var insets = UIEdgeInsets(top: 8, left: 0, bottom: 0, right: 0) var didSelectItemEnable: Bool = true @@ -30,14 +51,11 @@ class NCActivity: UIViewController, NCSharePagingContent { var isFetchingActivity = false var hasActivityToLoad = true { - didSet { - tableView.tableFooterView?.isHidden = hasActivityToLoad - } + didSet { tableView.tableFooterView?.isHidden = hasActivityToLoad } } var dateAutomaticFetch: Date? private let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! - @MainActor var session: NCSession.Session { if account.isEmpty { NCSession.shared.getSession(controller: tabBarController) @@ -51,7 +69,7 @@ class NCActivity: UIViewController, NCSharePagingContent { override func viewDidLoad() { super.viewDidLoad() - navigationController?.setNavigationBarAppearance() + self.navigationController?.navigationBar.prefersLargeTitles = true view.backgroundColor = .systemBackground self.title = NSLocalizedString("_activity_", comment: "") @@ -72,14 +90,7 @@ class NCActivity: UIViewController, NCSharePagingContent { commentView = Bundle.main.loadNibNamed("NCActivityCommentView", owner: self, options: nil)?.first as? NCActivityCommentView commentView?.setup(account: metadata.account) { newComment in guard let newComment = newComment, !newComment.isEmpty, let metadata = self.metadata else { return } - NextcloudKit.shared.putComments(fileId: metadata.fileId, message: newComment, account: metadata.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: metadata.account, - path: metadata.fileId, - name: "putComments") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { _, _, error in + NextcloudKit.shared.putComments(fileId: metadata.fileId, message: newComment, account: metadata.account) { _, _, error in if error == .success { self.commentView?.newCommentField.text?.removeAll() self.loadComments() @@ -101,12 +112,9 @@ class NCActivity: UIViewController, NCSharePagingContent { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - Task { - await NCNetworking.shared.networkingTasks.cancel(identifier: "NCActivity") - } - // Cancel Queue & Retrieves Properties NCNetworking.shared.downloadThumbnailActivityQueue.cancelAll() + dataSourceTask?.cancel() } override func viewWillLayoutSubviews() { @@ -225,8 +233,8 @@ extension NCActivity: UITableViewDataSource { cell.fileAvatarImageView?.image = results.image } - if let tblAvatar = results.tblAvatar, - !tblAvatar.loaded, + if let tableAvatar = results.tableAvatar, + !tableAvatar.loaded, NCNetworking.shared.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty { NCNetworking.shared.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: comment.actorId, fileName: fileName, account: account, view: tableView)) } @@ -271,7 +279,7 @@ extension NCActivity: UITableViewDataSource { if !activity.icon.isEmpty { activity.icon = activity.icon.replacingOccurrences(of: ".png", with: ".svg") let fileNameIcon = (activity.icon as NSString).lastPathComponent - let fileNameLocalPath = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileNameIcon) + let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + fileNameIcon if FileManager.default.fileExists(atPath: fileNameLocalPath) { let image = fileNameIcon.contains(".svg") ? SVGKImage(contentsOfFile: fileNameLocalPath)?.uiImage : UIImage(contentsOfFile: fileNameLocalPath) @@ -280,14 +288,7 @@ extension NCActivity: UITableViewDataSource { cell.icon.image = image.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) } } else { - NextcloudKit.shared.downloadContent(serverUrl: activity.icon, account: activity.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.account, - path: activity.icon, - name: "downloadContent") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { _, responseData, error in + NextcloudKit.shared.downloadContent(serverUrl: activity.icon, account: activity.account) { _, responseData, error in if error == .success, let data = responseData?.data { do { try data.write(to: NSURL(fileURLWithPath: fileNameLocalPath) as URL, options: .atomic) @@ -313,7 +314,7 @@ extension NCActivity: UITableViewDataSource { cell.fileAvatarImageView?.image = results.image } - if !(results.tblAvatar?.loaded ?? false), + if !(results.tableAvatar?.loaded ?? false), NCNetworking.shared.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty { NCNetworking.shared.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: activity.user, fileName: fileName, account: session.account, view: tableView)) } @@ -337,7 +338,7 @@ extension NCActivity: UITableViewDataSource { for key in keys { if let result = database.getActivitySubjectRich(account: session.account, idActivity: activity.idActivity, key: key) { orderKeysId.append(result.id) - subject = subject.replacingOccurrences(of: "{\(key)}", with: "" + result.name.escapedForMarkup + "") + subject = subject.replacingOccurrences(of: "{\(key)}", with: "" + result.name + "") } } @@ -376,8 +377,12 @@ extension NCActivity { func fetchAll(isInitial: Bool) { guard !isFetchingActivity else { return } self.isFetchingActivity = true + var bottom: CGFloat = 0 - NCActivityIndicator.shared.start(backgroundView: self.view, style: .medium) + if let mainTabBar = self.tabBarController?.tabBar as? NCMainTabBar { + bottom = -mainTabBar.getHeight() + } + NCActivityIndicator.shared.start(backgroundView: self.view, bottom: bottom - 35, style: .medium) let dispatchGroup = DispatchGroup() loadComments(disptachGroup: dispatchGroup) @@ -424,14 +429,7 @@ extension NCActivity { guard showComments, let metadata = metadata else { return } disptachGroup?.enter() - NextcloudKit.shared.getComments(fileId: metadata.fileId, account: metadata.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: metadata.account, - path: metadata.fileId, - name: "getComments") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { _, comments, _, error in + NextcloudKit.shared.getComments(fileId: metadata.fileId, account: metadata.account) { _, comments, _, error in if error == .success, let comments = comments { self.database.addComments(comments, account: metadata.account, objectId: metadata.fileId) } else if error.errorCode != NCGlobal.shared.errorResourceNotFound { @@ -448,13 +446,6 @@ extension NCActivity { /// Check if most recent activivities are loaded, if not trigger reload func checkRecentActivity(disptachGroup: DispatchGroup) { - Task { - // If is already in-flight, do nothing - if await NCNetworking.shared.networkingTasks.isReading(identifier: "NCActivity") { - return - } - } - guard let result = database.getLatestActivityId(account: session.account), metadata == nil, hasActivityToLoad else { return self.loadActivity(idActivity: 0, disptachGroup: disptachGroup) } @@ -462,15 +453,14 @@ extension NCActivity { disptachGroup.enter() - NextcloudKit.shared.getActivity(since: 0, - limit: 1, - objectId: nil, - objectType: objectType, - previews: true, - account: session.account) { task in - Task { - await NCNetworking.shared.networkingTasks.track(identifier: "NCActivity", task: task) - } + NextcloudKit.shared.getActivity( + since: 0, + limit: 1, + objectId: nil, + objectType: objectType, + previews: true, + account: session.account) { task in + self.dataSourceTask = task } completion: { account, _, activityFirstKnown, activityLastGiven, _, error in defer { disptachGroup.leave() } @@ -488,21 +478,18 @@ extension NCActivity { } func loadActivity(idActivity: Int, limit: Int = 200, disptachGroup: DispatchGroup) { - guard hasActivityToLoad else { - return - } + guard hasActivityToLoad else { return } var resultActivityId = 0 disptachGroup.enter() - NextcloudKit.shared.getActivity(since: idActivity, - limit: min(limit, 200), - objectId: metadata?.fileId, - objectType: objectType, - previews: true, - account: session.account) { task in - Task { - await NCNetworking.shared.networkingTasks.track(identifier: "NCActivity", task: task) - } + NextcloudKit.shared.getActivity( + since: idActivity, + limit: min(limit, 200), + objectId: metadata?.fileId, + objectType: objectType, + previews: true, + account: session.account) { task in + self.dataSourceTask = task } completion: { account, activities, activityFirstKnown, activityLastGiven, _, error in defer { disptachGroup.leave() } guard error == .success, @@ -531,26 +518,22 @@ extension NCActivity: NCShareCommentsCellDelegate { guard let tableComment = tableComment else { return } - self.showProfileMenu(userId: tableComment.actorId, session: session, sender: sender) + self.showProfileMenu(userId: tableComment.actorId, session: session) } func tapMenu(with tableComments: tableComments?, sender: Any) { - toggleMenu(with: tableComments, sender: sender) + toggleMenu(with: tableComments) } - func toggleMenu(with tableComments: tableComments?, sender: Any) { + func toggleMenu(with tableComments: tableComments?) { 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 - } + 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)) @@ -562,14 +545,7 @@ extension NCActivity: NCShareCommentsCellDelegate { 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 + NextcloudKit.shared.updateComments(fileId: metadata.fileId, messageId: tableComments.messageId, message: message, account: metadata.account) { _, _, error in if error == .success { self.loadComments() } else { @@ -588,18 +564,10 @@ extension NCActivity: NCShareCommentsCellDelegate { 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 + NextcloudKit.shared.deleteComments(fileId: metadata.fileId, messageId: tableComments.messageId, account: metadata.account) { _, _, error in if error == .success { self.loadComments() } else { @@ -610,6 +578,6 @@ extension NCActivity: NCShareCommentsCellDelegate { ) ) - presentMenu(with: actions, sender: sender) + presentMenu(with: actions) } } diff --git a/iOSClient/Extensions/DateFormatter+Extension.swift b/iOSClient/Extensions/DateFormatter+Extension.swift index 5d6a004843..2344faac6c 100644 --- a/iOSClient/Extensions/DateFormatter+Extension.swift +++ b/iOSClient/Extensions/DateFormatter+Extension.swift @@ -28,6 +28,7 @@ extension DateFormatter { static let shareExpDate: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.formatterBehavior = .behavior10_4 +// dateFormatter.locale = Locale.current dateFormatter.dateStyle = .medium dateFormatter.dateFormat = NCShareAdvancePermission.displayDateFormat return dateFormatter @@ -35,9 +36,13 @@ extension DateFormatter { } extension Date { - static var tomorrow: Date { return Date().dayAfter } - static var today: Date {return Date()} - var dayAfter: Date { + static var tomorrow: Date { return Date().dayAfter } + static var today: Date {return Date()} + static var dayAfterYear: Date { return Date().dateAfterYear } + var dayAfter: Date { return Calendar.current.date(byAdding: .day, value: 1, to: Date())! - } + } + var dateAfterYear: Date { + return Calendar.current.date(byAdding: .year, value: 1, to: Date())! + } } diff --git a/iOSClient/Extensions/UIToolbar+Extension.swift b/iOSClient/Extensions/UIToolbar+Extension.swift index c457f56493..ac403c9791 100644 --- a/iOSClient/Extensions/UIToolbar+Extension.swift +++ b/iOSClient/Extensions/UIToolbar+Extension.swift @@ -37,7 +37,7 @@ extension UIToolbar { buttons.append(clearButton) } buttons.append(UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil)) - let doneButton = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: .plain) { + let doneButton = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: .done) { onDone() } buttons.append(doneButton) diff --git a/iOSClient/Extensions/UIView+Extension.swift b/iOSClient/Extensions/UIView+Extension.swift index ef211e00f2..4673ca902c 100644 --- a/iOSClient/Extensions/UIView+Extension.swift +++ b/iOSClient/Extensions/UIView+Extension.swift @@ -51,6 +51,15 @@ extension UIView { hiddenView.addSubview(view) } + func addBlur(style: UIBlurEffect.Style) { + let blur = UIBlurEffect(style: style) + let blurredEffectView = UIVisualEffectView(effect: blur) + blurredEffectView.frame = self.bounds + blurredEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + blurredEffectView.isUserInteractionEnabled = false + self.addSubview(blurredEffectView) + } + func insertBlur(style: UIBlurEffect.Style) { let blur = UIBlurEffect(style: style) let blurredEffectView = UIVisualEffectView(effect: blur) @@ -65,17 +74,6 @@ extension UIView { self.layer.cornerRadius = self.frame.size.width / 2 self.layer.masksToBounds = true } - - var parentTabBarController: UITabBarController? { - var responder: UIResponder? = self - while let nextResponder = responder?.next { - if let tabBarController = nextResponder as? UITabBarController { - return tabBarController - } - responder = nextResponder - } - return nil - } func addShadow(location: VerticalLocation, height: CGFloat = 2, color: UIColor = NCBrandColor.shared.customerDarkGrey, opacity: Float = 0.4, radius: CGFloat = 2) { switch location { diff --git a/iOSClient/Menu/NCShare+Menu.swift b/iOSClient/Menu/NCShare+Menu.swift index 7829c1a926..6dc908d2db 100644 --- a/iOSClient/Menu/NCShare+Menu.swift +++ b/iOSClient/Menu/NCShare+Menu.swift @@ -26,26 +26,16 @@ import UIKit import NextcloudKit extension NCShare { - func toggleShareMenu(for share: tableShare, sender: Any?) { - let capabilities = NKCapabilities.shared.getCapabilitiesBlocking(for: self.metadata.account) -// func toggleShareMenu(for share: tableShare, sendMail: Bool, folder: Bool, sender: Any) { + func toggleShareMenu(for share: tableShare, sendMail: Bool, folder: Bool, sender: Any) { + + let capabilities = NCCapabilities.shared.getCapabilities(account: self.metadata.account) var actions = [NCMenuAction]() - if share.shareType == NCShareCommon.shareTypeLink, canReshare { + if !folder { actions.append( NCMenuAction( - title: NSLocalizedString("_share_add_sharelink_", comment: ""), - icon: utility.loadImage(named: "plus", colors: [NCBrandColor.shared.iconImageColor]), - sender: sender, -// func toggleShareMenu(for share: tableShare, sendMail: Bool, folder: Bool, sender: Any) { -// -// var actions = [NCMenuAction]() -// -// if !folder { -// actions.append( -// NCMenuAction( -// title: NSLocalizedString("_open_in_", comment: ""), -// icon: utility.loadImage(named: "viewInFolder").imageColor(NCBrandColor.shared.brandElement), + title: NSLocalizedString("_open_in_", comment: ""), + icon: utility.loadImage(named: "viewInFolder").imageColor(NCBrandColor.shared.brandElement), action: { _ in NCShareCommon().copyLink(link: share.url, viewController: self, sender: sender) } @@ -55,14 +45,11 @@ extension NCShare { actions.append( NCMenuAction( - title: NSLocalizedString("_advance_permissions_", comment: ""), - icon: utility.loadImage(named: "rename").imageColor(NCBrandColor.shared.brandElement), - accessibilityIdentifier: "shareMenu/details", - sender: sender, // title: NSLocalizedString("_details_", comment: ""), // icon: utility.loadImage(named: "pencil", colors: [NCBrandColor.shared.iconImageColor]), // accessibilityIdentifier: "shareMenu/details", - + title: NSLocalizedString("_advance_permissions_", comment: ""), + icon: utility.loadImage(named: "rename").imageColor(NCBrandColor.shared.brandElement), action: { _ in guard let advancePermission = UIStoryboard(name: "NCShare", bundle: nil).instantiateViewController(withIdentifier: "NCShareAdvancePermission") as? NCShareAdvancePermission, @@ -101,17 +88,16 @@ extension NCShare { actions.append( NCMenuAction( title: NSLocalizedString("_share_unshare_", comment: ""), - destructive: true, icon: utility.loadImage(named: "trash").imageColor(NCBrandColor.shared.brandElement), - sender: sender, action: { _ in Task { - if share.shareType != NCShareCommon.shareTypeLink, let metadata = self.metadata, metadata.e2eEncrypted && capabilities.e2EEApiVersion == NCGlobal.shared.e2eeVersionV20 { - if await NCNetworkingE2EE().isInUpload(account: metadata.account, serverUrl: metadata.serverUrlFileName) { + if share.shareType != NCShareCommon().SHARE_TYPE_LINK, let metadata = self.metadata, metadata.e2eEncrypted && capabilities.capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 { + let serverUrl = metadata.serverUrl + "/" + metadata.fileName + 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: metadata.serverUrlFileName, addUserId: nil, removeUserId: share.shareWith, account: metadata.account) + let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, addUserId: nil, removeUserId: share.shareWith, account: metadata.account) if error != .success { return NCContentPresenter().showError(error: error) } @@ -122,84 +108,49 @@ extension NCShare { ) ) - self.presentMenu(with: actions, sender: sender) + self.presentMenu(with: actions) } - func toggleQuickPermissionsMenu(isDirectory: Bool, share: tableShare, sender: Any?) { + func toggleUserPermissionMenu(isDirectory: Bool, tableShare: tableShare) { var actions = [NCMenuAction]() + let permissions = NCPermissions() - actions.append(contentsOf: - [NCMenuAction( + actions.append( + NCMenuAction( title: NSLocalizedString("_share_read_only_", comment: ""), - icon: utility.loadImage(named: "eye", colors: [NCBrandColor.shared.iconImageColor]), - selected: share.permissions == (permissions.permissionReadShare + permissions.permissionShareShare) || share.permissions == permissions.permissionReadShare, -// icon: UIImage(), -// selected: tableShare.permissions == (NCGlobal.shared.permissionReadShare + NCGlobal.shared.permissionShareShare) || tableShare.permissions == NCGlobal.shared.permissionReadShare, + icon: UIImage(), + selected: tableShare.permissions == (permissions.permissionReadShare + permissions.permissionShareShare) || tableShare.permissions == permissions.permissionReadShare, 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) + 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) } - ), + ) + ) + + actions.append( NCMenuAction( -// title: NSLocalizedString("_share_editing_", comment: ""), - title: isDirectory ? NSLocalizedString("_share_allow_upload_", comment: "") : NSLocalizedString("_share_editing_", comment: ""), - icon: utility.loadImage(named: "pencil", colors: [NCBrandColor.shared.iconImageColor]), - selected: hasUploadPermission(tableShare: share), -// icon: UIImage(), -// selected: hasUploadPermission(tableShare: tableShare), +// title: isDirectory ? NSLocalizedString("_share_allow_upload_", comment: "") : NSLocalizedString("_share_editing_", comment: ""), + title: NSLocalizedString("_share_editing_", comment: ""), + icon: UIImage(), + selected: hasUploadPermission(tableShare: tableShare), 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) + 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) } - ), - 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) if isDirectory, NCShareCommon().isFileDropOptionVisible(isDirectory: isDirectory, shareType: tableShare.shareType) { actions.append( NCMenuAction( title: NSLocalizedString("_share_file_drop_", comment: ""), - icon: tableShare.permissions == permissions.permissionCreateShare ? UIImage(named: "success")?.image(color: NCBrandColor.shared.customer, size: 25.0) ?? UIImage() : UIImage(), - selected: false, + icon: UIImage(), + selected: tableShare.permissions == permissions.permissionCreateShare, on: false, action: { menuAction in self.updateSharePermissions(share: tableShare, permissions: permissions.permissionCreateShare) @@ -208,15 +159,16 @@ extension NCShare { ) } - self.presentMenu(with: actions, sender: sender) + self.presentMenu(with: actions) } fileprivate func hasUploadPermission(tableShare: tableShare) -> Bool { + let permissions = NCPermissions() let uploadPermissions = [ - NCSharePermissions.permissionMaxFileShare, - NCSharePermissions.permissionMaxFolderShare, - NCSharePermissions.permissionDefaultFileRemoteShareNoSupportShareOption, - NCSharePermissions.permissionDefaultFolderRemoteShareNoSupportShareOption] + permissions.permissionMaxFileShare, + permissions.permissionMaxFolderShare, + permissions.permissionDefaultFileRemoteShareNoSupportShareOption, + permissions.permissionDefaultFolderRemoteShareNoSupportShareOption] return uploadPermissions.contains(tableShare.permissions) } @@ -230,11 +182,8 @@ 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!") + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Failed to get download limit from database!") return } diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index b3efc3ba5c..77b553560b 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -4,18 +4,34 @@ import UIKit -final class NCGlobal: Sendable { - static let shared = NCGlobal() - - init() { } +/// Used for read/write in Realm +var isAppSuspending: Bool = false +/// Used for know if the app in in Background mode +var isAppInBackground: Bool = false +class NCGlobal: NSObject, @unchecked Sendable { + @objc static let shared = NCGlobal() + + override init() { + super.init() + NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { _ in + isAppSuspending = true + isAppInBackground = true + } + + NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { _ in + isAppSuspending = false + isAppInBackground = false + } + } + // ENUM // public enum TypeFilterScanDocument: String { case document = "document" case original = "original" } - + // Directory on Group // let directoryProviderStorage = "File Provider Storage" @@ -24,13 +40,13 @@ final class NCGlobal: Sendable { let appDatabaseNextcloud = "Library/Application Support/Nextcloud" let appScan = "Library/Application Support/Scan" let appUserData = "Library/Application Support/UserData" - + // Service // let metadataKeyedUnarchiver = "it.twsweb.nextcloud.metadata" let refreshTask = "com.nextcloud.refreshTask" let processingTask = "com.nextcloud.processingTask" - + // App // let appName = "files" @@ -39,9 +55,10 @@ final class NCGlobal: Sendable { let spreedName = "spreed" let twoFactorNotificatioName = "twofactor_nextcloud_notification" let termsOfServiceName = "terms_of_service" - + // Nextcloud version // + let nextcloudVersion12: Int = 12 let nextcloudVersion18: Int = 18 let nextcloudVersion20: Int = 20 let nextcloudVersion23: Int = 23 @@ -52,21 +69,26 @@ final class NCGlobal: Sendable { let nextcloudVersion28: Int = 28 let nextcloudVersion30: Int = 30 let nextcloudVersion31: Int = 31 - + // Nextcloud unsupported // let nextcloud_unsupported_version: Int = 17 - + // Intro selector // - let introLogin: Int = 0 - let introSignUpWithProvider: Int = 1 - + @objc let introLogin: Int = 0 + let introSignup: Int = 1 + // let introSignUpWithProvider: Int = 1 + + // Varie size GUI + // + @objc let heightCellSettings: CGFloat = 50 + // Avatar // let avatarSize: Int = 128 * Int(UIScreen.main.scale) let avatarSizeRounded: Int = 128 - + // Preview size // let size1024: CGSize = CGSize(width: 1024, height: 1024) @@ -76,50 +98,50 @@ final class NCGlobal: Sendable { let previewExt1024 = ".1024.preview.jpg" let previewExt512 = ".512.preview.jpg" let previewExt256 = ".256.preview.jpg" - + func getSizeExtension(column: Int) -> String { if column == 0 { return previewExt256 } let width = UIScreen.main.bounds.width / CGFloat(column) - - switch (width * 4) { - case 0...384: - return previewExt256 - case 385...768: - return previewExt512 - default: - return previewExt1024 - } + + switch (width * 4) { + case 0...384: + return previewExt256 + case 385...768: + return previewExt512 + default: + return previewExt1024 + } } - + // E2EE // let e2eePassphraseTest = "more over television factory tendency independence international intellectual impress interest sentence pony" - let e2eeVersions = ["1.1", "1.2", "2.0"] + @objc let e2eeVersions = ["1.1", "1.2", "2.0"] let e2eeVersionV11 = "1.1" let e2eeVersionV12 = "1.2" let e2eeVersionV20 = "2.0" - + // CHUNK let chunkSizeMBCellular = 10000000 let chunkSizeMBEthernetOrWiFi = 100000000 - + // Video // let maxHTTPCache: Int64 = 10000000000 // 10 GB let fileNameVideoEncoded: String = "video_encoded.mp4" - + // NCViewerProviderContextMenu // let maxAutoDownload: UInt64 = 50000000 // 50MB let maxAutoDownloadCellular: UInt64 = 10000000 // 10MB - + // Layout // let layoutList = "typeLayoutList" let layoutGrid = "typeLayoutGrid" let layoutPhotoRatio = "typeLayoutPhotoRatio" let layoutPhotoSquare = "typeLayoutPhotoSquare" - + let layoutViewTrash = "LayoutTrash" let layoutViewOffline = "LayoutOffline" let layoutViewFavorite = "LayoutFavorite" @@ -130,21 +152,52 @@ final class NCGlobal: Sendable { let layoutViewShareExtension = "LayoutShareExtension" let layoutViewGroupfolders = "LayoutGroupfolders" let layoutViewMedia = "LayoutMedia" + let layoutViewMove = "LayoutMove" + // Button Type in Cell list/grid // let buttonMoreMore = "more" let buttonMoreLock = "moreLock" + let buttonMoreStop = "stop" + + // Standard height sections header/footer + // + let heightButtonsView: CGFloat = 50 + let heightHeaderTransfer: CGFloat = 50 + let heightSection: CGFloat = 30 + let heightFooter: CGFloat = 1 + let heightFooterButton: CGFloat = 30 + let endHeightFooter: CGFloat = 85 + + + // Text - OnlyOffice - Collabora - QuickLook + // + let editorText = "text" + let editorOnlyoffice = "onlyoffice" + let editorCollabora = "collabora" + let editorQuickLook = "quicklook" + + let onlyofficeDocx = "onlyoffice_docx" + let onlyofficeXlsx = "onlyoffice_xlsx" + let onlyofficePptx = "onlyoffice_pptx" + + // Template + // + let templateDocument = "document" + let templateSpreadsheet = "spreadsheet" + let templatePresentation = "presentation" + // Rich Workspace // let fileNameRichWorkspace = "Readme.md" - + // ContentPresenter // let dismissAfterSecond: TimeInterval = 4 let dismissAfterSecondLong: TimeInterval = 7 - + // Error // let errorRequestExplicityCancelled: Int = 15 @@ -157,6 +210,7 @@ final class NCGlobal: Sendable { let errorConflict: Int = 409 let errorPreconditionFailed: Int = 412 let errorUnsupportedMediaType: Int = 415 + let errorWebDAVLocked: Int = 423 let errorInternalServerError: Int = 500 let errorMaintenance: Int = 503 let errorQuota: Int = 507 @@ -174,16 +228,12 @@ final class NCGlobal: Sendable { let errorUnauthorizedFilesPasscode: Int = -99993 let errorDisableFilesApp: Int = -99992 let errorUnexpectedResponseFromDB: Int = -99991 - let errorIncorrectFileName: Int = -99990 - let errorVersionMismatch: Int = -99989 - let errorNCSessionNotFound: Int = -99988 - // E2EE let errorE2EENotEnabled: Int = -98000 let errorE2EEVersion: Int = -98001 let errorE2EEKeyChecksums: Int = -98002 let errorE2EEKeyEncodeMetadata: Int = -98003 - let errorE2EEKeyDecodeMetadataV12: Int = -98004 + let errorE2EEKeyDecodeMetadata: Int = -98004 let errorE2EEKeyVerifySignature: Int = -98005 let errorE2EEKeyCiphertext: Int = -98006 let errorE2EEKeyFiledropCiphertext: Int = -98007 @@ -196,9 +246,7 @@ final class NCGlobal: Sendable { let errorE2EEEncodedKey: Int = -98014 let errorE2EENoUserFound: Int = -98015 let errorE2EEUploadInProgress: Int = -98016 - let errorE2EEKeyDirectoryTop: Int = -98017 - - + // Selector // let selectorDownloadFile = "downloadFile" @@ -208,6 +256,7 @@ final class NCGlobal: Sendable { let selectorLoadFileQuickLook = "loadFileQuickLook" let selectorOpenIn = "openIn" let selectorUploadAutoUpload = "uploadAutoUpload" + let selectorUploadAutoUploadAll = "uploadAutoUploadAll" let selectorUploadFile = "uploadFile" let selectorUploadFileNODelete = "UploadFileNODelete" let selectorUploadFileShareExtension = "uploadFileShareExtension" @@ -215,7 +264,9 @@ final class NCGlobal: Sendable { let selectorSaveAsScan = "saveAsScan" let selectorOpenDetail = "openDetail" let selectorSynchronizationOffline = "synchronizationOffline" - + let selectorPrint = "print" + let selectorDeleteFile = "deleteFile" + // Metadata : Status // // 0 normal @@ -224,54 +275,101 @@ final class NCGlobal: Sendable { // ± 3 error // let metadataStatusNormal: Int = 0 - + let metadataStatusWaitDownload: Int = -1 let metadataStatusDownloading: Int = -2 let metadataStatusDownloadError: Int = -3 - + let metadataStatusWaitUpload: Int = 1 let metadataStatusUploading: Int = 2 let metadataStatusUploadError: Int = 3 - + let metadataStatusWaitCreateFolder: Int = 10 let metadataStatusWaitDelete: Int = 11 let metadataStatusWaitRename: Int = 12 let metadataStatusWaitFavorite: Int = 13 let metadataStatusWaitCopy: Int = 14 let metadataStatusWaitMove: Int = 15 - + let metadataStatusUploadingAllMode = [1,2,3] - let metadataStatusDownloadingAllMode = [-1, -2, -3] - let metadataStatusForScreenAwake = [-1, -2, 1, 2] + let metadataStatusInTransfer = [-1, -2, 1, 2] + let metadataStatusFileDown = [-1, -2, -3] let metadataStatusHideInView = [1, 2, 3, 11] + let metadataStatusHideInFileExtension = [1, 2, 3, 10, 11] let metadataStatusWaitWebDav = [10, 11, 12, 13, 14, 15] + + let metadataStatusObserveNetworkingProcess = [-1, 1, 10, 11, 12, 13, 14, 15] + let metadataStatusObserveTrasfers = [-2, 2, 10, 11, 12, 13, 14, 15] + // Hidden files included in the read + // + let includeHiddenFiles: [String] = [".LivePhoto"] + // Auto upload subfolder granularity // - let subfolderGranularityDaily = 2 - let subfolderGranularityMonthly = 1 - let subfolderGranularityYearly = 0 - + @objc let subfolderGranularityDaily = 2 + @objc let subfolderGranularityMonthly = 1 + @objc let subfolderGranularityYearly = 0 + // Notification Center // - let notificationCenterChangeUser = "changeUser" // userInfo: account, controller - let notificationCenterChangeTheming = "changeTheming" // userInfo: account + @objc let notificationCenterChangeUser = "changeUser" + let notificationCenterChangeTheming = "changeTheming" + @objc let notificationCenterApplicationDidEnterBackground = "applicationDidEnterBackground" + @objc let notificationCenterApplicationDidBecomeActive = "applicationDidBecomeActive" + @objc let notificationCenterApplicationWillResignActive = "applicationWillResignActive" + @objc let notificationCenterApplicationWillEnterForeground = "applicationWillEnterForeground" + + + @objc let notificationCenterInitialize = "initialize" let notificationCenterRichdocumentGrabFocus = "richdocumentGrabFocus" let notificationCenterReloadDataNCShare = "reloadDataNCShare" + let notificationCenterDidCreateShareLink = "didCreateShareLink" + let notificationCenterCloseRichWorkspaceWebView = "closeRichWorkspaceWebView" let notificationCenterReloadAvatar = "reloadAvatar" + let notificationCenterReloadHeader = "reloadHeader" let notificationCenterClearCache = "clearCache" + let notificationCenterChangeLayout = "changeLayout" // userInfo: account, serverUrl, layoutForView let notificationCenterCheckUserDelaultErrorDone = "checkUserDelaultErrorDone" // userInfo: account, controller - let notificationCenterServerDidUpdate = "serverDidUpdate" // userInfo: account + let notificationCenterCreateMediaCacheEnded = "createMediaCacheEnded" + let notificationCenterUpdateNotification = "updateNotification" + let notificationCenterReloadDataSource = "reloadDataSource" // userInfo: serverUrl?, clearDataSource + let notificationCenterGetServerData = "getServerData" // userInfo: serverUrl? + + let notificationCenterChangeStatusFolderE2EE = "changeStatusFolderE2EE" // userInfo: serverUrl + + let notificationCenterDownloadStartFile = "downloadStartFile" // userInfo: ocId, ocIdTransfer, session, serverUrl, account + let notificationCenterDownloadedFile = "downloadedFile" // userInfo: ocId, ocIdTransfer, session, session, serverUrl, account, selector, error + let notificationCenterDownloadCancelFile = "downloadCancelFile" // userInfo: ocId, ocIdTransfer, session, serverUrl, account + + let notificationCenterUploadStartFile = "uploadStartFile" // userInfo: ocId, ocIdTransfer, session, serverUrl, account, fileName, sessionSelector + let notificationCenterUploadedFile = "uploadedFile" // userInfo: ocId, ocIdTransfer, session, serverUrl, account, fileName, ocIdTransfer, error + let notificationCenterUploadedLivePhoto = "uploadedLivePhoto" // userInfo: ocId, ocIdTransfer, session, serverUrl, account, fileName, ocIdTransfer, error + let notificationCenterUploadCancelFile = "uploadCancelFile" // userInfo: ocId, ocIdTransfer, session, serverUrl, account + + let notificationCenterProgressTask = "progressTask" // userInfo: account, ocId, ocIdTransfer, session, serverUrl, status, chunk, e2eEncrypted, progress, totalBytes, totalBytesExpected + + let notificationCenterUpdateBadgeNumber = "updateBadgeNumber" // userInfo: counterDownload, counterUpload + + let notificationCenterCreateFolder = "createFolder" // userInfo: ocId, serverUrl, account, withPush, sceneIdentifier + let notificationCenterDeleteFile = "deleteFile" // userInfo: [ocId], error + let notificationCenterCopyMoveFile = "copyMoveFile" // userInfo: [ocId] serverUrl, account, dragdrop, type (copy, move) + let notificationCenterMoveFile = "moveFile" // userInfo: [ocId], [indexPath], error + let notificationCenterCopyFile = "copyFile" // userInfo: [ocId], [indexPath], error + let notificationCenterRenameFile = "renameFile" // userInfo: serverUrl, account, error + let notificationCenterFavoriteFile = "favoriteFile" // userInfo: ocId, serverUrl + let notificationCenterFileExists = "fileExists" // userInfo: ocId, fileExists + let notificationCenterMenuSearchTextPDF = "menuSearchTextPDF" let notificationCenterMenuGotToPageInPDF = "menuGotToPageInPDF" - + let notificationCenterOpenMediaDetail = "openMediaDetail" // userInfo: ocId - + let notificationCenterDismissScanDocument = "dismissScanDocument" let notificationCenterDismissUploadAssets = "dismissUploadAssets" - + let notificationCenterEnableSwipeGesture = "enableSwipeGesture" let notificationCenterDisableSwipeGesture = "disableSwipeGesture" @@ -282,38 +380,25 @@ final class NCGlobal: Sendable { let notificationCenterStatusReadOnly = "statusReadOnly" let notificationCenterStatusEditing = "statusEditing" let notificationCenterStatusFileDrop = "statusFileDrop" + let notificationCenterPlayerIsPlaying = "playerIsPlaying" let notificationCenterPlayerStoppedPlaying = "playerStoppedPlaying" + + let notificationCenterUpdateShare = "updateShare" + let notificationCenterShareCountsUpdated = "shareCountsUpdated" + let notificationCenterUpdateIcons = "updateIcons" - // Networking Status - let networkingStatusCreateFolder = "statusCreateFolder" - let networkingStatusDelete = "statusDelete" - let networkingStatusRename = "statusRename" - let networkingStatusFavorite = "statusFavorite" - - let networkingStatusDownloading = "statusDownloading" - let networkingStatusDownloaded = "statusDownloaded" - let networkingStatusDownloadCancel = "statusDownloadCancel" - - let networkingStatusUploading = "statusUploading" - let networkingStatusUploaded = "statusUploaded" - let networkingStatusUploadedLivePhoto = "statusUploadedLivePhoto" - let networkingStatusUploadCancel = "statusUploadCancel" - - - let networkingStatusReloadAvatar = "statusReloadAvatar" - - + // TIP // let tipPDFThumbnail = "tipPDFThumbnail" let tipAccountRequest = "tipAccountRequest" let tipScanAddImage = "tipScanAddImage" let tipMediaDetailView = "tipMediaDetailView" - let tipAutoUploadButton = "tipAutoUploadButton" - + let tipAutoUpload = "tipAutoUpload" + // ACTION // let actionNoAction = "no-action" @@ -321,7 +406,7 @@ final class NCGlobal: Sendable { let actionScanDocument = "add-scan-document" let actionTextDocument = "create-text-document" let actionVoiceMemo = "create-voice-memo" - + // WIDGET ACTION // let widgetActionNoAction = "nextcloud://open-action?action=no-action" @@ -329,24 +414,45 @@ final class NCGlobal: Sendable { let widgetActionScanDocument = "nextcloud://open-action?action=add-scan-document" let widgetActionTextDocument = "nextcloud://open-action?action=create-text-document" let widgetActionVoiceMemo = "nextcloud://open-action?action=create-voice-memo" - + // APPCONFIG // let configuration_brand = "brand" - + let configuration_serverUrl = "serverUrl" let configuration_username = "username" let configuration_password = "password" let configuration_apppassword = "apppassword" - + let configuration_disable_intro = "disable_intro" let configuration_disable_multiaccount = "disable_multiaccount" let configuration_disable_crash_service = "disable_crash_service" let configuration_disable_log = "disable_log" + let configuration_disable_manage_account = "disable_manage_account" let configuration_disable_more_external_site = "disable_more_external_site" let configuration_disable_openin_file = "disable_openin_file" let configuration_enforce_passcode_lock = "enforce_passcode_lock" - + + // CAPABILITIES + // + var capabilityServerVersionMajor: Int = 0 + @objc var capabilityServerVersion: String = "" + @objc var capabilityThemingName: String = "" + @objc var capabilityThemingSlogan: String = "" + + @objc var capabilityE2EEEnabled: Bool = false + @objc var capabilityE2EEApiVersion: String = "" + + var capabilityRichdocumentsEnabled: Bool = false + var capabilityRichdocumentsMimetypes: [String] = [] + var capabilityActivity: [String] = [] + var capabilityNotification: [String] = [] + + @objc var capabilityUserStatusEnabled: Bool = false + var isLivePhotoServerAvailable: Bool { // NC28 + return capabilityServerVersionMajor >= nextcloudVersion28 + } + // MORE NEXTCLOUD APPS // let talkSchemeUrl = "nextcloudtalk://" @@ -354,55 +460,57 @@ final class NCGlobal: Sendable { let talkAppStoreUrl = "https://apps.apple.com/in/app/nextcloud-talk/id1296825574" let notesAppStoreUrl = "https://apps.apple.com/in/app/nextcloud-notes/id813973264" let moreAppsUrl = "itms-apps://search.itunes.apple.com/WebObjects/MZSearch.woa/wa/search?media=software&term=nextcloud" - + // SNAPSHOT PREVIEW // let defaultSnapshotConfiguration = "DefaultPreviewConfiguration" - + +// // FORBIDDEN CHARACTERS +// // +// // TODO: Remove this +// let forbiddenCharacters = ["/", "\\", ":", "\"", "|", "?", "*", "<", ">"] + // DIAGNOSTICS CLIENTS // let diagnosticIssueSyncConflicts = "sync_conflicts" let diagnosticIssueProblems = "problems" let diagnosticIssueVirusDetected = "virus_detected" let diagnosticIssueE2eeErrors = "e2ee_errors" - + let diagnosticProblemsForbidden = "CHARACTERS_FORBIDDEN" let diagnosticProblemsBadResponse = "BAD_SERVER_RESPONSE" let diagnosticProblemsUploadServerError = "UploadError.SERVER_ERROR" - + // MEDIA LAYOUT // let mediaLayoutRatio = "mediaLayoutRatio" let mediaLayoutSquare = "mediaLayoutSquare" - + // DRAG & DROP // let metadataOcIdDataRepresentation = "text/com.nextcloud.ocId" - + // GROUP AMIN // let groupAdmin = "admin" - - // TASK DESCRIPTION + + // DATA TASK DESCRIPTION // let taskDescriptionRetrievesProperties = "retrievesProperties" let taskDescriptionSynchronization = "synchronization" - - // LOG TAG - // - let logTagTask = "BGT" - let logTagLocation = "LOCATION" - let logTagBgSync = "BGSYNC" - let logTagE2EE = "E2EE" - let logTagPN = "PUSH NOTIFICATION" - let logTagSync = "SYNC" - let logTagServiceProficer = "SERVICE PROVIDER" - let logTagDatabase = "DB" - let logSpeedUpSyncMetadata = "SYNC METADATA" - let logNetworkingTasks = "NETWORKING TASKS" - - // USER DEFAULTS + let taskDescriptionDeleteFileOrFolder = "deleteFileOrFolder" + + // MoEngage App Version // - let udMigrationMultiDomains = "migrationMultiDomains" - let udLastVersion = "lastVersion" + let moEngageAppVersion = 854 + + // Filename Mask and Type + // + let keyFileNameMask = "fileNameMask" + let keyFileNameType = "fileNameType" + let keyFileNameAutoUploadMask = "fileNameAutoUploadMask" + let keyFileNameAutoUploadType = "fileNameAutoUploadType" + let keyFileNameOriginal = "fileNameOriginal" + let keyFileNameOriginalAutoUpload = "fileNameOriginalAutoUpload" + } diff --git a/iOSClient/Share/Advanced/DownloadLimit/DownloadLimitViewModel.swift b/iOSClient/Share/Advanced/DownloadLimit/DownloadLimitViewModel.swift index a949484cae..4d7b7d54f0 100644 --- a/iOSClient/Share/Advanced/DownloadLimit/DownloadLimitViewModel.swift +++ b/iOSClient/Share/Advanced/DownloadLimit/DownloadLimitViewModel.swift @@ -20,3 +20,19 @@ enum DownloadLimitViewModel { /// case limited(limit: Int, count: Int) } + +extension DownloadLimitViewModel { + var limit: Int? { + if case let .limited(limit, _) = self { + return limit + } + return nil + } + + var count: Int? { + if case let .limited(_, count) = self { + return count + } + return nil + } +} diff --git a/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitTableViewController.swift b/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitTableViewController.swift index 13999e437e..86db4128dc 100644 --- a/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitTableViewController.swift +++ b/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitTableViewController.swift @@ -32,12 +32,9 @@ class NCShareDownloadLimitTableViewController: UITableViewController { /// Default value for limits as possibly provided by the server capabilities. /// var defaultLimit: Int { - let capabilities = NCNetworking.shared.capabilities[metadata.account] ?? NKCapabilities.Capabilities() - return capabilities.fileSharingDownloadLimitDefaultLimit + NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityFileSharingDownloadLimitDefaultLimit } - @IBOutlet var allowedDownloadsCell: UITableViewCell! - @IBOutlet var limitDownloadCell: UITableViewCell! @IBOutlet var limitSwitch: UISwitch! @IBOutlet var limitTextField: UITextField! @@ -50,14 +47,6 @@ class NCShareDownloadLimitTableViewController: UITableViewController { } else { limitSwitch.isOn = false } - - var allowedDownloadsCellConfiguration = UIListContentConfiguration.cell() - allowedDownloadsCellConfiguration.text = NSLocalizedString("_share_allowed_downloads_", comment: "") - allowedDownloadsCell.contentConfiguration = allowedDownloadsCellConfiguration - - var limitDownloadCellConfiguration = UIListContentConfiguration.cell() - limitDownloadCellConfiguration.text = NSLocalizedString("_share_limit_download_", comment: "") - limitDownloadCell.contentConfiguration = limitDownloadCellConfiguration } @IBAction func switchDownloadLimit(_ sender: UISwitch) { diff --git a/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitViewController.swift b/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitViewController.swift index f21e12f356..e3d89e6fc5 100644 --- a/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitViewController.swift +++ b/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitViewController.swift @@ -16,14 +16,19 @@ class NCShareDownloadLimitViewController: UIViewController, NCShareNavigationTit public var shareDownloadLimitTableViewControllerDelegate: NCShareDownloadLimitTableViewControllerDelegate? @IBOutlet var headerContainerView: UIView! +// private var headerView: NCShareAdvancePermissionHeader? override func viewDidLoad() { super.viewDidLoad() self.setNavigationTitle() +// NotificationCenter.default.addObserver(self, selector: #selector(handleShareCountsUpdate), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterShareCountsUpdated), object: nil) + // Set up header view. +// setupHeaderView() - guard let headerView = (Bundle.main.loadNibNamed("NCShareHeader", owner: self, options: nil)?.first as? NCShareHeader) else { return } + guard let headerView = (Bundle.main.loadNibNamed("NCShareAdvancePermissionHeader", owner: self, options: nil)?.first as? NCShareAdvancePermissionHeader) else { return } +// guard let headerView = (Bundle.main.loadNibNamed("NCShareHeader", owner: self, options: nil)?.first as? NCShareHeader) else { return } headerContainerView.addSubview(headerView) headerView.frame = headerContainerView.frame headerView.translatesAutoresizingMaskIntoConstraints = false @@ -36,7 +41,7 @@ class NCShareDownloadLimitViewController: UIViewController, NCShareNavigationTit // End editing of inputs when the user taps anywhere else. - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard(_:))) + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) view.addGestureRecognizer(tapGesture) } @@ -51,7 +56,38 @@ class NCShareDownloadLimitViewController: UIViewController, NCShareNavigationTit tableViewController.share = share } - @objc private func dismissKeyboard(_ sender: Any?) { + @objc private func dismissKeyboard() { view.endEditing(true) } + +// // MARK: - Header +// +// private func setupHeaderView() { +// guard headerView == nil else { return } // Prevent multiple creations +// guard let view = Bundle.main.loadNibNamed("NCShareAdvancePermissionHeader", owner: self, options: nil)?.first as? NCShareAdvancePermissionHeader else { return } +// +// headerView = view +// headerContainerView.addSubview(view) +// +// // Auto Layout +// view.translatesAutoresizingMaskIntoConstraints = false +// NSLayoutConstraint.activate([ +// view.topAnchor.constraint(equalTo: headerContainerView.topAnchor), +// view.bottomAnchor.constraint(equalTo: headerContainerView.bottomAnchor), +// view.leadingAnchor.constraint(equalTo: headerContainerView.leadingAnchor), +// view.trailingAnchor.constraint(equalTo: headerContainerView.trailingAnchor) +// ]) +// +// // Initial setup +// headerView?.setupUI(with: metadata) +// } +// +// @objc private func handleShareCountsUpdate(notification: Notification) { +// guard let userInfo = notification.userInfo, +// let links = userInfo["links"] as? Int, +// let emails = userInfo["emails"] as? Int else { return } +// +// // Just update, don’t recreate +// headerView?.setupUI(with: metadata, linkCount: links, emailCount: emails) +// } } diff --git a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift index 682f50d9e5..208b3fb7b2 100644 --- a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift +++ b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift @@ -27,9 +27,31 @@ import SVGKit import CloudKit import XLForm -class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDelegate, NCShareNavigationTitleSetting { -//class NCShareAdvancePermission: XLFormViewController, NCShareAdvanceFotterDelegate, NCShareDetail { +class NCShareAdvancePermission: XLFormViewController, 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 + } + if shouldSave { self.oldTableShare?.permissions = self.permission ?? (self.oldTableShare?.permissions ?? 0) self.share.permissions = self.permission ?? (self.oldTableShare?.permissions ?? 0) @@ -44,23 +66,56 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg if let downloadSwitchCell = getDownloadLimitSwitchCell() { let isDownloadLimitOn = downloadSwitchCell.switchControl.isOn if !isDownloadLimitOn { - setDownloadLimit(deleteLimit: true, limit: "") + setDownloadLimit(deleteLimit: true, limit: String(defaultLimit)) } else { let downloadLimitInputCell = getDownloadLimitInputCell() - let enteredDownloadLimit = downloadLimitInputCell?.cellTextField.text ?? "" +// let enteredDownloadLimit = downloadLimitInputCell?.cellTextField.text ?? "" + let enteredDownloadLimit = downloadLimitInputCell?.cellTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + +// if enteredDownloadLimit.isEmpty { +// showDownloadLimitError(message: NSLocalizedString("_share_download_limit_alert_empty_", comment: "")) +// return +// } +// if let num = Int(enteredDownloadLimit), num < 1 { +// showDownloadLimitError(message: NSLocalizedString("_share_download_limit_alert_zero_", comment: "")) +// return +// } +// +// self.downloadLimit = .limited(limit: Int(enteredDownloadLimit)!, count: downloadLimit.count ?? 0) + + // Case 1: Empty input if enteredDownloadLimit.isEmpty { showDownloadLimitError(message: NSLocalizedString("_share_download_limit_alert_empty_", comment: "")) return } - if let num = Int(enteredDownloadLimit), num < 1 { + + // Case 2: Non-numeric or too large + guard let num = Int(enteredDownloadLimit) else { + // Input is either not a number or exceeds Int range + showDownloadLimitError(message: NSLocalizedString("_share_download_limit_alert_invalid_", comment: "")) + return + } + + // Case 3: Zero or negative + if num < 1 { showDownloadLimitError(message: NSLocalizedString("_share_download_limit_alert_zero_", comment: "")) return } + + // ✅ Safe assignment + self.downloadLimit = .limited(limit: num, count: downloadLimit.count ?? 0) setDownloadLimit(deleteLimit: false, limit: enteredDownloadLimit) + updateDownloadLimitUI() } } - networking?.updateShare(option: share) + if let expiryDateSwitchCell = getExpiryDateSwitchCell() { + let isExpiryDateOn = expiryDateSwitchCell.switchControl.isOn + if !isExpiryDateOn { + share.expirationDate = nil + } + } + networking?.updateShare(share, downloadLimit: self.downloadLimit) navigationController?.popViewController(animated: true) } } else { @@ -101,43 +156,55 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg isNewShare ? share.shareType : oldTableShare?.shareType ?? NCShareCommon().SHARE_TYPE_USER }() static let displayDateFormat = "dd. MMM. yyyy" - var downloadLimit: DownloadLimit? var permission: Int? + /// + /// Default value for limits as possibly provided by the server capabilities. + /// + var defaultLimit: Int { + NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityFileSharingDownloadLimitDefaultLimit + } + override func viewDidLoad() { super.viewDidLoad() self.shareConfig = NCShareConfig(parentMetadata: metadata, share: share) - - // Only persisted shares have tokens which are provided by the server. - // A download limit requires a token to exist. - // Hence it can only be looked up if the share is already persisted at this point. - if isNewShare == false { - if let persistedShare = share as? tableShare { - do { - if let limit = try database.getDownloadLimit(byAccount: metadata.account, shareToken: persistedShare.token) { - self.downloadLimit = .limited(limit: limit.limit, count: limit.count) - } - } catch { - nkLog(error: "There was an error while fetching the download limit for share with token \(persistedShare.token)!") - } - } - } - - tableView.estimatedRowHeight = tableView.rowHeight - tableView.rowHeight = UITableView.automaticDimension - self.setNavigationTitle() // disbale pull to dimiss isModalInPresentation = true self.tableView.separatorStyle = UITableViewCell.SeparatorStyle.none self.permission = oldTableShare?.permissions + if !isNewShare { + Task { + await getDownloadLimit() + } + } initializeForm() changeTheming() - getDownloadLimit() +// getDownloadLimit() +// Task { +// await getDownloadLimit() +// } + +// // Only persisted shares have tokens which are provided by the server. +// // A download limit requires a token to exist. +// // Hence it can only be looked up if the share is already persisted at this point. +// if isNewShare == false { +// if let persistedShare = share as? tableShare { +// do { +// if let limit = try database.getDownloadLimit(byAccount: metadata.account, shareToken: persistedShare.token) { +// self.downloadLimit = .limited(limit: limit.limit, count: limit.count) +// self.updateDownloadLimitUI() +// } +// } catch { +// NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] There was an error while fetching the download limit for share with token \(persistedShare.token)!") +// } +// } +// } + NotificationCenter.default.addObserver(self, selector: #selector(changeTheming), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeTheming), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) +// NotificationCenter.default.addObserver(self, selector: #selector(handleShareCountsUpdate), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterShareCountsUpdated), object: nil) } override func viewWillLayoutSubviews() { @@ -147,6 +214,17 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg setupFooterView() } +// override func viewWillLayoutSubviews() { +// super.viewWillLayoutSubviews() +// +// if tableView.tableHeaderView == nil { +// setupHeaderView() +// } +// if tableView.tableFooterView == nil { +// setupFooterView() +// } +// } + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { if (UIDevice.current.userInterfaceIdiom == .phone), UIDevice().hasNotch { let isLandscape = UIDevice.current.orientation.isLandscape @@ -155,16 +233,34 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg tableView.layoutIfNeeded() } } - - @objc func keyboardWillShow(_ notification:Notification) { - if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { - tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height + 60, right: 0) + + @objc func keyboardWillShow(_ notification: Notification) { + guard let userInfo = notification.userInfo else { return } + + // Use the end frame for the keyboard + if let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { + + // Adjust the content inset of the table view + let keyboardHeight = keyboardFrame.height + let extraPadding: CGFloat = 60 + + // Animate the content inset change to match the keyboard's animation + UIView.animate(withDuration: 0.3, animations: { + self.tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight + extraPadding, right: 0) + self.tableView.scrollIndicatorInsets = self.tableView.contentInset + }) } } - - @objc func keyboardWillHide(_ notification:Notification) { - if ((notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue) != nil { - tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: tableViewBottomInset, right: 0) + + @objc func keyboardWillHide(_ notification: Notification) { + guard let userInfo = notification.userInfo else { return } + + // Reset the content inset when the keyboard is hidden + if let _ = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue) { + UIView.animate(withDuration: 0.3, animations: { + self.tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: self.tableViewBottomInset, right: 0) + self.tableView.scrollIndicatorInsets = self.tableView.contentInset + }) } } @@ -202,39 +298,34 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg headerView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true } - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - if section == 0 { - return NSLocalizedString("_custom_permissions_", comment: "") - } else if section == 1 { - return NSLocalizedString("_advanced_", comment: "") - } else { return nil } - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return 2 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if section == 0 { - // check reshare permission, if restricted add note - let maxPermission = metadata.directory ? NCSharePermissions.permissionMaxFolderShare : NCSharePermissions.permissionMaxFileShare - return shareConfig.sharePermission != maxPermission ? shareConfig.permissions.count + 1 : shareConfig.permissions.count - } else if section == 1 { - return shareConfig.advanced.count - } else { return 0 } - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = shareConfig.cellFor(indexPath: indexPath) else { - let noteCell = UITableViewCell(style: .subtitle, reuseIdentifier: "noteCell") - noteCell.detailTextLabel?.text = NSLocalizedString("_share_reshare_restricted_", comment: "") - noteCell.detailTextLabel?.isEnabled = false - noteCell.isUserInteractionEnabled = false - noteCell.detailTextLabel?.numberOfLines = 0 - return noteCell - } - } - +// @objc private func handleShareCountsUpdate(notification: Notification) { +// guard let userInfo = notification.userInfo, +// let links = userInfo["links"] as? Int, +// let emails = userInfo["emails"] as? Int else { +// return +// } +// +// // Update existing header if possible +// if let header = tableView.tableHeaderView as? NCShareAdvancePermissionHeader { +// header.setupUI(with: metadata, linkCount: links, emailCount: emails) +// } else { +// // Fallback: re-create header if missing +// setupHeaderView(linkCount: links, emailCount: emails) +// } +// } +// +// func setupHeaderView(linkCount: Int = 0, emailCount: Int = 0) { +// guard let headerView = Bundle.main.loadNibNamed("NCShareAdvancePermissionHeader", owner: self, options: nil)?.first as? NCShareAdvancePermissionHeader else { +// return +// } +// +// headerView.setupUI(with: metadata, linkCount: linkCount, emailCount: emailCount) +// headerView.ocId = metadata.ocId +// headerView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 190) +// +// tableView.tableHeaderView = headerView +// } + func initializeForm() { let form : XLFormDescriptor var section : XLFormSectionDescriptor @@ -284,8 +375,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg } } let enabled = NCShareCommon().isEditingEnabled(isDirectory: metadata.directory, fileExtension: metadata.fileExtension, shareType: shareType) || checkIsCollaboraFile() - row.cellConfig["titleLabel.textColor"] = enabled ? NCBrandColor.shared.label : NCBrandColor.shared.systemGray - row.disabled = !enabled + row.cellConfig["titleLabel.textColor"] = NCBrandColor.shared.label section.addFormRow(row) if !enabled { @@ -313,6 +403,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg row = XLFormRowDescriptor(tag: "kNMCFilePermissionCellFiledropMessage", rowType: "kNMCFilePermissionCell", title: NSLocalizedString("_PERMISSIONS_", comment: "")) row.cellConfig["titleLabel.text"] = NSLocalizedString("_file_drop_message_", comment: "") row.cellConfig["titleLabel.textColor"] = NCBrandColor.shared.gray60 +// row.cellConfig["titleLabel.textColor"] = NCBrandColor.shared.label row.cellConfig["imageCheck.image"] = UIImage() row.height = 84 section.addFormRow(row) @@ -393,6 +484,11 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg row.cellConfig["fileNameInputTextField.textColor"] = NCBrandColor.shared.label row.cellConfig["backgroundColor"] = NCBrandColor.shared.secondarySystemGroupedBackground row.height = 44 + +// // Add validator for min 6 characters +// guard let minLengthValidator = XLFormRegexValidator(msg: NSLocalizedString("_password_min_6_chars_", comment: "Password must be at least 6 characters"), andRegexString: "^.{6,}$") else { return <#default value#> } +// row.addValidator(minLengthValidator) + let hasPassword = oldTableShare?.password != nil && !oldTableShare!.password.isEmpty row.hidden = NSNumber.init(booleanLiteral: !hasPassword) section.addFormRow(row) @@ -425,6 +521,12 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg if let date = oldTableShare?.expirationDate { row.cellConfig["cellTextField.text"] = DateFormatter.shareExpDate.string(from: date as Date) } +// else { +// let nextYearTomorrow = Date.tomorrowNextYear +// row.cellConfig["cellTextField.text"] = DateFormatter.shareExpDate.string(from: nextYearTomorrow) +// share.expirationDate = nextYearTomorrow as NSDate +// } + row.height = 44 let hasExpiry = oldTableShare?.expirationDate != nil row.hidden = NSNumber.init(booleanLiteral: !hasExpiry) @@ -449,10 +551,11 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg row.cellConfig["cellTextField.font"] = UIFont.systemFont(ofSize: 15.0) row.cellConfig["cellTextField.textColor"] = NCBrandColor.shared.label row.height = 44 - let downloadLimitSet = downloadLimit?.limit != nil - row.hidden = NSNumber.init(booleanLiteral: !downloadLimitSet) - if let value = downloadLimit?.limit { - row.cellConfig["cellTextField.text"] = "\(value)" + if case let .limited(limit, _) = downloadLimit { + row.hidden = NSNumber(booleanLiteral: false) + row.cellConfig["cellTextField.text"] = "\(limit)" + } else { + row.hidden = NSNumber(booleanLiteral: true) } section.addFormRow(row) @@ -460,12 +563,15 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg row = XLFormRowDescriptor(tag: "kNMCDownloadLimitCell", rowType: "kNMCFilePermissionCell", title: "") row.cellClass = NCFilePermissionCell.self row.height = 44 - if downloadLimit?.limit != nil { - row.cellConfig["titleLabel.text"] = NSLocalizedString("_share_remaining_download_", comment: "") + " \(downloadLimit?.count ?? 0)" + switch downloadLimit { + case .limited(_, let count): + row.cellConfig["titleLabel.text"] = NSLocalizedString("_share_remaining_download_", comment: "") + " \(count)" + row.hidden = false + default: + row.hidden = true } - row.cellConfig["titleLabel.textColor"] = NCBrandColor.shared.systemGray + row.cellConfig["titleLabel.textColor"] = NCBrandColor.shared.systemGray row.disabled = true - row.hidden = NSNumber.init(booleanLiteral: !downloadLimitSet) section.addFormRow(row) } @@ -480,7 +586,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg } func updateDownloadLimitUI() { - if let value = downloadLimit?.limit { + if let value = downloadLimit.limit { if let downloadLimitSwitchField: XLFormRowDescriptor = self.form.formRow(withTag: "kNMCFilePermissionEditCellDownloadLimit") { if let indexPath = self.form.indexPath(ofFormRow: downloadLimitSwitchField) { let cell = tableView.cellForRow(at: indexPath) as? NCFilePermissionEditCell @@ -499,7 +605,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg downloadLimitInputField.hidden = false if let indexPath = self.form.indexPath(ofFormRow: downloadLimitInputField) { let cell = tableView.cellForRow(at: indexPath) as? NCFilePermissionCell - cell?.titleLabel.text = NSLocalizedString("_share_remaining_download_", comment: "") + " \(downloadLimit?.count ?? 0)" + cell?.titleLabel.text = NSLocalizedString("_share_remaining_download_", comment: "") + " \(downloadLimit.count ?? 0)" } } } @@ -526,6 +632,16 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg return nil } + func getExpiryDateSwitchCell() -> NCFilePermissionEditCell? { + if let expiryRow : XLFormRowDescriptor = self.form.formRow(withTag: "kNMCFilePermissionEditCellExpiration") { + if let indexPath = self.form.indexPath(ofFormRow: expiryRow) { + let cell = tableView.cellForRow(at: indexPath) as? NCFilePermissionEditCell + return cell + } + } + return nil + } + // MARK: - Row Descriptor Value Changed override func didSelectFormRow(_ formRow: XLFormRowDescriptor!) { @@ -586,15 +702,6 @@ 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? NCAdvancedPermission else { - cellConfig.didSelect(for: share) - tableView.reloadData() - } - } - func canReshareTheShare() -> Bool { if let permissionValue = self.permission { let canReshare = NCPermissions().isPermissionToCanShare(permissionValue) @@ -667,7 +774,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg if let limitRow : XLFormRowDescriptor = self.form.formRow(withTag: "kNMCFilePermissionEditCellDownloadLimit") { if let expiryIndexPath = self.form.indexPath(ofFormRow: limitRow), indexPath == expiryIndexPath { let cell = cell as? NCFilePermissionEditCell - cell?.switchControl.isOn = downloadLimit?.limit != nil + cell?.switchControl.isOn = downloadLimit.limit != nil } } @@ -675,16 +782,16 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg if let downloadlimitFieldRow : XLFormRowDescriptor = self.form.formRow(withTag: "NCShareTextInputCellDownloadLimit") { if let downloadlimitIndexPath = self.form.indexPath(ofFormRow: downloadlimitFieldRow), indexPath == downloadlimitIndexPath { let cell = cell as? NCShareTextInputCell - cell?.cellTextField.text = "\(downloadLimit?.limit ?? 0)" + cell?.cellTextField.text = "\(downloadLimit.limit ?? 0)" } } //SetDownloadLimitSwitch - if downloadLimit?.count != nil { + if downloadLimit.count != nil { if let downloadlimitFieldRow : XLFormRowDescriptor = self.form.formRow(withTag: "kNMCDownloadLimitCell") { if let downloadlimitIndexPath = self.form.indexPath(ofFormRow: downloadlimitFieldRow), indexPath == downloadlimitIndexPath { let cell = cell as? NCFilePermissionCell - cell?.titleLabel.text = NSLocalizedString("_share_remaining_download_", comment: "") + " \(downloadLimit?.count ?? 0)" + cell?.titleLabel.text = NSLocalizedString("_share_remaining_download_", comment: "") + " \(downloadLimit.count ?? 0)" cell?.seperatorBelowFull.isHidden = true cell?.seperatorBelow.isHidden = true } @@ -733,6 +840,36 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg self.form.delegate = self } +// case "SetPasswordInputField": +// if let pwd = formRow.value as? String { +// self.form.delegate = nil +// +// // Validate password length +// if pwd.count < 6 { +// share.password = "" +// +// // Show alert for invalid password +// let alert = UIAlertController( +// title: NSLocalizedString("_invalid_password_title_", comment: "Invalid Password"), +// message: NSLocalizedString("_password_min_length_", comment: "Password must be at least 6 characters"), +// preferredStyle: .alert +// ) +// alert.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: "OK"), style: .default)) +// self.present(alert, animated: true) +// +// // Clear the text field in the cell +// if let indexPath = self.form.indexPath(ofFormRow: formRow), +// let cell = tableView.cellForRow(at: indexPath) as? PasswordInputField { +// cell.fileNameInputTextField.text = "" +// } +// } else { +// // Password is valid +// share.password = pwd +// } +// +// self.form.delegate = self +// } + case "kNMCFilePermissionEditCellLinkLabel": if let label = formRow.value as? String { self.form.delegate = nil @@ -740,22 +877,50 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg self.form.delegate = self } +// case "kNMCFilePermissionEditCellExpiration": +// if let value = newValue as? Bool { +// if let inputField : XLFormRowDescriptor = self.form.formRow(withTag: "NCShareTextInputCellExpiry") { +// inputField.hidden = !value +// } +// } + case "kNMCFilePermissionEditCellExpiration": if let value = newValue as? Bool { - if let inputField : XLFormRowDescriptor = self.form.formRow(withTag: "NCShareTextInputCellExpiry") { + if let inputField: XLFormRowDescriptor = self.form.formRow(withTag: "NCShareTextInputCellExpiry") { inputField.hidden = !value + + // Only set expiration date if toggle is ON and there's no existing value + if value && share.expirationDate == nil { + let defaultExpiry = Date.dayNextYear + inputField.value = defaultExpiry + share.expirationDate = defaultExpiry as NSDate + + // Reload the cell to update the UI + if let indexPath = self.form.indexPath(ofFormRow: inputField) { + // Reload row to ensure the text field is updated +// self.tableView.reloadRows(at: [indexPath], with: .automatic) + + // Manually update the cell's text field to display the expiration date + if let cell = self.tableView.cellForRow(at: indexPath) as? NCShareTextInputCell { + // Use DateFormatter to format the date + let formattedDate = DateFormatter.shareExpDate.string(from: defaultExpiry) + cell.cellTextField.text = formattedDate + } + } + } } } case "kNMCFilePermissionEditCellDownloadLimit": if let value = newValue as? Bool { - self.downloadLimit = DownloadLimit() - self.downloadLimit?.limit = value ? 0 : nil + + self.downloadLimit = value ? .limited(limit: 0, count: 0) : .unlimited + if let inputField : XLFormRowDescriptor = self.form.formRow(withTag: "NCShareTextInputCellDownloadLimit") { inputField.hidden = !value if let indexPath = self.form.indexPath(ofFormRow: inputField) { let cell = tableView.cellForRow(at: indexPath) as? NCShareTextInputCell - cell?.cellTextField.text = "" + cell?.cellTextField.text = "\(downloadLimit.limit ?? 0)" } } @@ -765,7 +930,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg let cell = tableView.cellForRow(at: indexPath) as? NCFilePermissionCell cell?.seperatorBelowFull.isHidden = true cell?.seperatorBelow.isHidden = true - cell?.titleLabel.text = "" + cell?.titleLabel.text = "" //String(defaultLimit) } } } @@ -775,6 +940,11 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg self.form.delegate = nil self.share.expirationDate = exp as NSDate self.form.delegate = self + } else { + // Only clear if the row value was *explicitly* set to nil by the user + if formRow.value == nil { + self.share.expirationDate = nil + } } default: @@ -855,106 +1025,46 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg self.permission = permissions.getPermission(canEdit: false, canCreate: false, canChange: false, canDelete: false, canShare: isOn, isDirectory: metadata.directory) } } - let alertController = UIAlertController.password(titleKey: "_share_password_") { password in - self.share.password = password ?? "" - tableView.reloadData() - } - self.present(alertController, animated: true) -// case .note: -// let storyboard = UIStoryboard(name: "NCShare", bundle: nil) -// guard let viewNewUserComment = storyboard.instantiateViewController(withIdentifier: "NCShareNewUserAddComment") as? NCShareNewUserAddComment else { return } -// viewNewUserComment.metadata = self.metadata -// viewNewUserComment.share = self.share -// viewNewUserComment.onDismiss = tableView.reloadData -// self.navigationController?.pushViewController(viewNewUserComment, animated: true) -// case .label: -// let alertController = UIAlertController.withTextField(titleKey: "_share_link_name_") { textField in -// textField.placeholder = cellConfig.title -// textField.text = self.share.label -// } completion: { newValue in -// self.share.label = newValue ?? "" -// self.setNavigationTitle() -// tableView.reloadData() -// } -// self.present(alertController, animated: true) -// case .downloadAndSync: -// share.downloadAndSync.toggle() -// tableView.reloadData() } } + + func getDownloadLimit() async { + NCActivityIndicator.shared.start(backgroundView: view) - 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.shareTypeLink || share.shareType == NCShareCommon.shareTypeEmail) && NCSharePermissions.hasPermissionToShare(share.permissions) { - share.permissions = share.permissions - NCSharePermissions.permissionReshareShare - } - - if isNewShare { - let capabilities = await NKCapabilities.shared.getCapabilities(for: metadata.account) - - if share.shareType != NCShareCommon.shareTypeLink, metadata.e2eEncrypted, - capabilities.e2EEApiVersion == NCGlobal.shared.e2eeVersionV20 { + do { + // First, try to fetch download limits from the server. + try await networking?.readDownloadLimits(account: metadata.account, tokens: [oldTableShare?.token ?? ""]) + NCActivityIndicator.shared.stop() // Stop the activity indicator - 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) + if !isNewShare, let persistedShare = share as? tableShare { + // If not a new share and share exists, fetch the limit for that persisted share + do { + if let limit = try database.getDownloadLimit(byAccount: metadata.account, shareToken: persistedShare.token) { + DispatchQueue.main.async { + NCActivityIndicator.shared.stop() // Stop the activity indicator + self.downloadLimit = .limited(limit: limit.limit, count: limit.count) + self.updateDownloadLimitUI() + } } - - let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: metadata.serverUrlFileName, addUserId: share.shareWith, removeUserId: nil, account: metadata.account) - - if error != .success { - return NCContentPresenter().showError(error: error) + } catch { + // Handle the error for the persisted share token lookup + DispatchQueue.main.async { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Error fetching the download limit for share with token \(persistedShare.token).") + NCActivityIndicator.shared.stop() // Stop the activity indicator } } - - networking?.createShare(share, downloadLimit: self.downloadLimit) - } else { - networking?.updateShare(share, downloadLimit: self.downloadLimit) } - } - - navigationController?.popViewController(animated: true) - } - - func getDownloadLimit() { - NCActivityIndicator.shared.start(backgroundView: view) - NMCCommunication.shared.getDownloadLimit(token: oldTableShare?.token ?? "") { [weak self] (downloadLimit: DownloadLimit?, error: String) in + } catch { + // Handle error for fetching download limits from the server DispatchQueue.main.async { - NCActivityIndicator.shared.stop() - if let downloadLimit = downloadLimit { - self?.downloadLimit = downloadLimit - } - self?.updateDownloadLimitUI() + print("Error fetching download limit: \(error)") + NCActivityIndicator.shared.stop() // Stop the activity indicator } } } - + func setDownloadLimit(deleteLimit: Bool, limit: String) { - NMCCommunication.shared.setDownloadLimit(deleteLimit: deleteLimit, limit: limit, token: oldTableShare?.token ?? "") { (success, errorMessage) in - print(success) - } + networking?.setShareDownloadLimit(Int(limit) ?? 0, token: oldTableShare?.token ?? "") } func showDownloadLimitError(message: String) { @@ -971,3 +1081,15 @@ extension NCShareAdvancePermission: NCShareDownloadLimitTableViewControllerDeleg self.downloadLimit = downloadLimit } } + +extension Date { + static var dayNextYear: Date { +// let calendar = Calendar.current +// let now = Date() +// var components = DateComponents() +// components.year = 1 +// components.day = 1 +// return calendar.date(byAdding: components, to: now)! + return Calendar.current.date(byAdding: .year, value: 1, to: Date())! + } +} diff --git a/iOSClient/Share/Advanced/NCShareAdvancePermissionFooter.swift b/iOSClient/Share/Advanced/NCShareAdvancePermissionFooter.swift index 455b5da0c6..64d8f6d9eb 100644 --- a/iOSClient/Share/Advanced/NCShareAdvancePermissionFooter.swift +++ b/iOSClient/Share/Advanced/NCShareAdvancePermissionFooter.swift @@ -42,17 +42,6 @@ class NCShareAdvancePermissionFooter: UIView { buttonCancel.layer.cornerRadius = 10 buttonCancel.layer.masksToBounds = true buttonCancel.layer.borderWidth = 1 - buttonCancel.layer.borderColor = NCBrandColor.shared.textColor2.cgColor - buttonCancel.backgroundColor = .secondarySystemBackground - buttonCancel.addTarget(self, action: #selector(cancelClicked(_:)), for: .touchUpInside) - buttonCancel.setTitleColor(NCBrandColor.shared.textColor2, for: .normal) - - buttonNext.setTitle(NSLocalizedString(delegate?.isNewShare == true ? "_share_" : "_save_", comment: ""), for: .normal) - buttonNext.layer.cornerRadius = 25 - buttonNext.layer.masksToBounds = true - buttonNext.backgroundColor = NCBrandColor.shared.getElement(account: account) - buttonNext.addTarget(self, action: #selector(nextClicked(_:)), for: .touchUpInside) - addShadow(location: .top) layer.cornerRadius = 10 layer.masksToBounds = true @@ -66,11 +55,11 @@ class NCShareAdvancePermissionFooter: UIView { buttonNext.layer.masksToBounds = true } - @objc func cancelClicked(_ sender: Any?) { + @objc func cancelClicked() { delegate?.dismissShareAdvanceView(shouldSave: false) } - @objc func nextClicked(_ sender: Any?) { + @objc func nextClicked() { delegate?.dismissShareAdvanceView(shouldSave: true) } } diff --git a/iOSClient/Share/Advanced/NCShareAdvancePermissionHeader.xib b/iOSClient/Share/Advanced/NCShareAdvancePermissionHeader.xib index 161dc96732..20cda3faac 100644 --- a/iOSClient/Share/Advanced/NCShareAdvancePermissionHeader.xib +++ b/iOSClient/Share/Advanced/NCShareAdvancePermissionHeader.xib @@ -1,34 +1,33 @@ - + - - + - + - + - + - @@ -62,7 +60,7 @@ - + @@ -75,7 +73,7 @@ - + diff --git a/iOSClient/Share/Advanced/NCShareCells.swift b/iOSClient/Share/Advanced/NCShareCells.swift index e812b5241c..b898b1e22b 100644 --- a/iOSClient/Share/Advanced/NCShareCells.swift +++ b/iOSClient/Share/Advanced/NCShareCells.swift @@ -22,8 +22,6 @@ // import UIKit -import OSLog -import NextcloudKit protocol NCShareCellConfig { var title: String { get } @@ -50,112 +48,154 @@ protocol NCPermission: NCToggleCellConfig { static var forDirectory: [Self] { get } static var forFile: [Self] { get } static func forDirectoryE2EE(account: String) -> [NCPermission] - func hasPermission(for parentPermission: Int) -> Bool - func hasReadPermission() -> Bool + func hasResharePermission(for parentPermission: Int) -> Bool + func hasDownload() -> Bool } enum NCUserPermission: CaseIterable, NCPermission { - func hasPermission(for parentPermission: Int) -> Bool { + func hasResharePermission(for parentPermission: Int) -> Bool { + if self == .download { return true } return ((permissionBitFlag & parentPermission) != 0) } - func hasReadPermission() -> Bool { - return self == .read + func hasDownload() -> Bool { + return self == .download } var permissionBitFlag: Int { - return switch self { - case .read: NCSharePermissions.permissionReadShare - case .reshare: NCSharePermissions.permissionReshareShare - case .edit: NCSharePermissions.permissionEditShare - case .create: NCSharePermissions.permissionCreateShare - case .delete: NCSharePermissions.permissionDeleteShare + switch self { + case .reshare: return NCPermissions().permissionShareShare + case .edit: return NCPermissions().permissionUpdateShare + case .create: return NCPermissions().permissionCreateShare + case .delete: return NCPermissions().permissionDeleteShare + case .download: return NCPermissions().permissionDownloadShare } } func didChange(_ share: Shareable, to newValue: Bool) { - share.permissions ^= permissionBitFlag + if self == .download { + share.attributes = NCManageDatabase.shared.setAttibuteDownload(state: newValue) + } else { + share.permissions ^= permissionBitFlag + } } func isOn(for share: Shareable) -> Bool { - return (share.permissions & permissionBitFlag) != 0 + if self == .download { + return NCManageDatabase.shared.isAttributeDownloadEnabled(attributes: share.attributes) + } else { + return (share.permissions & permissionBitFlag) != 0 + } } static func forDirectoryE2EE(account: String) -> [NCPermission] { - let capabilities = NCNetworking.shared.capabilities[account] ?? NKCapabilities.Capabilities() - if capabilities.e2EEApiVersion == NCGlobal.shared.e2eeVersionV20 { + if NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 { return NCUserPermission.allCases } return [] } - case read, reshare, edit, create, delete + case reshare, edit, create, delete, download static let forDirectory: [NCUserPermission] = NCUserPermission.allCases - static let forFile: [NCUserPermission] = [.read, .reshare, .edit] + static let forFile: [NCUserPermission] = [.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 NCLinkEmailPermission: CaseIterable, NCPermission { - static func forDirectoryE2EE(account: String) -> [any NCPermission] { - let capabilities = NCNetworking.shared.capabilities[account] ?? NKCapabilities.Capabilities() - if capabilities.e2EEApiVersion == NCGlobal.shared.e2eeVersionV20 { - return NCUserPermission.allCases +enum NCLinkPermission: NCPermission { + func didChange(_ share: Shareable, to newValue: Bool) { + guard self != .allowEdit || newValue else { + share.permissions = NCPermissions().permissionReadShare + return } - return [] + share.permissions = permissionValue } - func hasReadPermission() -> Bool { - return self == .read + func hasResharePermission(for parentPermission: Int) -> Bool { + permissionValue & parentPermission == permissionValue } - func hasPermission(for parentPermission: Int) -> Bool { - return ((permissionBitFlag & parentPermission) != 0) + func hasDownload() -> Bool { + return false } - var permissionBitFlag: Int { - return switch self { - case .read: NCSharePermissions.permissionReadShare - case .edit: NCSharePermissions.permissionEditShare - case .create: NCSharePermissions.permissionCreateShare - case .delete: NCSharePermissions.permissionDeleteShare + var permissionValue: 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 } } - func didChange(_ share: Shareable, to newValue: Bool) { - share.permissions ^= permissionBitFlag + 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 isOn(for share: Shareable) -> Bool { - return (share.permissions & permissionBitFlag) != 0 + static func forDirectoryE2EE(account: String) -> [NCPermission] { + return [NCLinkPermission.secureFileDrop] } var title: String { switch self { - 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: 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 edit, read, create, delete - static let forDirectory: [NCLinkEmailPermission] = NCLinkEmailPermission.allCases - static let forFile: [NCLinkEmailPermission] = [.read, .edit] + case allowEdit, viewOnly, uploadEdit, fileDrop, secureFileDrop + static let forDirectory: [NCLinkPermission] = [.viewOnly, .uploadEdit, .fileDrop] + static let forFile: [NCLinkPermission] = [.allowEdit] } /// /// Individual aspects of share. /// -enum NCAdvancedPermission: CaseIterable, NCShareCellConfig { +enum NCShareDetails: CaseIterable, NCShareCellConfig { func didSelect(for share: Shareable) { switch self { case .hideDownload: share.hideDownload.toggle() @@ -164,7 +204,6 @@ enum NCAdvancedPermission: CaseIterable, NCShareCellConfig { case .password: return case .note: return case .label: return - case .downloadAndSync: return } } @@ -189,8 +228,6 @@ enum NCAdvancedPermission: CaseIterable, NCShareCellConfig { let cell = UITableViewCell(style: .value1, reuseIdentifier: "shareLabel") cell.detailTextLabel?.text = share.label return cell - case .downloadAndSync: - return NCShareToggleCell(isOn: share.downloadAndSync) } } @@ -202,73 +239,56 @@ enum NCAdvancedPermission: 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, downloadAndSync - static let forLink: [NCAdvancedPermission] = [.expirationDate, .hideDownload, .label, .limitDownload, .note, .password] - static let forUser: [NCAdvancedPermission] = [.expirationDate, .note, .downloadAndSync] + case label, hideDownload, limitDownload, expirationDate, password, note + static let forLink: [NCShareDetails] = NCShareDetails.allCases + static let forUser: [NCShareDetails] = [.expirationDate, .note] } struct NCShareConfig { let permissions: [NCPermission] - let advanced: [NCAdvancedPermission] - let shareable: Shareable - let sharePermission: Int - let isDirectory: Bool + let advanced: [NCShareDetails] + let share: Shareable + let resharePermission: Int - /// 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.shareable = share - self.sharePermission = parentMetadata.sharePermissionsCollaborationServices - self.isDirectory = parentMetadata.directory - let type: NCPermission.Type = (share.shareType == NCShareCommon.shareTypeLink || share.shareType == NCShareCommon.shareTypeEmail) ? NCLinkEmailPermission.self : NCUserPermission.self + self.share = share + self.resharePermission = parentMetadata.sharePermissionsCollaborationServices + let type: NCPermission.Type = share.shareType == NCShareCommon().SHARE_TYPE_LINK ? NCLinkPermission.self : NCUserPermission.self self.permissions = parentMetadata.directory ? (parentMetadata.e2eEncrypted ? type.forDirectoryE2EE(account: parentMetadata.account) : type.forDirectory) : type.forFile - if share.shareType == NCShareCommon.shareTypeLink { - let capabilities = NCNetworking.shared.capabilities[parentMetadata.account] ?? NKCapabilities.Capabilities() - let hasDownloadLimitCapability = capabilities.fileSharingDownloadLimit + if share.shareType == NCShareCommon().SHARE_TYPE_LINK { + let hasDownloadLimitCapability = NCCapabilities + .shared + .getCapabilities(account: parentMetadata.account) + .capabilityFileSharingDownloadLimit if parentMetadata.isDirectory || hasDownloadLimitCapability == false { - self.advanced = NCAdvancedPermission.forLink.filter { $0 != .limitDownload } + self.advanced = NCShareDetails.forLink.filter { $0 != .limitDownload } } else { - self.advanced = NCAdvancedPermission.forLink + self.advanced = NCShareDetails.forLink } } else { - self.advanced = NCAdvancedPermission.forUser + self.advanced = NCShareDetails.forUser } } func cellFor(indexPath: IndexPath) -> UITableViewCell? { let cellConfig = config(for: indexPath) - let cell = cellConfig?.getCell(for: shareable) + let cell = cellConfig?.getCell(for: share) cell?.textLabel?.text = cellConfig?.title - Logger().info("\(cellConfig?.title ?? "")") - - if let cellConfig = cellConfig as? NCPermission, !cellConfig.hasPermission(for: sharePermission) { + if let cellConfig = cellConfig as? NCPermission, !cellConfig.hasResharePermission(for: resharePermission), !cellConfig.hasDownload() { 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: shareable) + cellConfig?.didSelect(for: share) } func config(for indexPath: IndexPath) -> NCShareCellConfig? { diff --git a/iOSClient/Share/Advanced/NCShareDateCell.swift b/iOSClient/Share/Advanced/NCShareDateCell.swift index 4af43ab227..deeb1cc715 100644 --- a/iOSClient/Share/Advanced/NCShareDateCell.swift +++ b/iOSClient/Share/Advanced/NCShareDateCell.swift @@ -2,8 +2,6 @@ // SPDX-FileCopyrightText: 2022 Henrik Storch // SPDX-License-Identifier: GPL-3.0-or-later -import NextcloudKit - /// /// Table view cell to manage the expiration date on a share in its details. /// @@ -12,6 +10,7 @@ class NCShareDateCell: UITableViewCell { let textField = UITextField() var shareType: Int var onReload: (() -> Void)? + let shareCommon = NCShareCommon() init(share: Shareable) { self.shareType = share.shareType @@ -59,42 +58,38 @@ class NCShareDateCell: UITableViewCell { } private func isExpireDateEnforced(account: String) -> Bool { - let capabilities = NCNetworking.shared.capabilities[account] ?? NKCapabilities.Capabilities() - switch self.shareType { - case NCShareCommon.shareTypeLink, - NCShareCommon.shareTypeEmail, - NCShareCommon.shareTypeGuest: - return capabilities.fileSharingPubExpireDateEnforced - case NCShareCommon.shareTypeUser, - NCShareCommon.shareTypeGroup, - NCShareCommon.shareTypeTeam, - NCShareCommon.shareTypeRoom: - return capabilities.fileSharingInternalExpireDateEnforced - case NCShareCommon.shareTypeFederated, - NCShareCommon.shareTypeFederatedGroup: - return capabilities.fileSharingRemoteExpireDateEnforced + case shareCommon.SHARE_TYPE_LINK, + shareCommon.SHARE_TYPE_EMAIL, + shareCommon.SHARE_TYPE_GUEST: + return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingPubExpireDateEnforced + case shareCommon.SHARE_TYPE_USER, + shareCommon.SHARE_TYPE_GROUP, + 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: + return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingRemoteExpireDateEnforced default: return false } } private func defaultExpirationDays(account: String) -> Int { - let capabilities = NCNetworking.shared.capabilities[account] ?? NKCapabilities.Capabilities() - switch self.shareType { - case NCShareCommon.shareTypeLink, - NCShareCommon.shareTypeEmail, - NCShareCommon.shareTypeGuest: - return capabilities.fileSharingPubExpireDateDays - case NCShareCommon.shareTypeUser, - NCShareCommon.shareTypeGroup, - NCShareCommon.shareTypeTeam, - NCShareCommon.shareTypeRoom: - return capabilities.fileSharingInternalExpireDateDays - case NCShareCommon.shareTypeFederated, - NCShareCommon.shareTypeFederatedGroup: - return capabilities.fileSharingRemoteExpireDateDays + case shareCommon.SHARE_TYPE_LINK, + shareCommon.SHARE_TYPE_EMAIL, + shareCommon.SHARE_TYPE_GUEST: + return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingPubExpireDateDays + case shareCommon.SHARE_TYPE_USER, + shareCommon.SHARE_TYPE_GROUP, + 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: + return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingRemoteExpireDateDays default: return 0 } diff --git a/iOSClient/Share/Advanced/NCShareNewUserAddComment.swift b/iOSClient/Share/Advanced/NCShareNewUserAddComment.swift index a7fe311e0c..c227d21c8a 100644 --- a/iOSClient/Share/Advanced/NCShareNewUserAddComment.swift +++ b/iOSClient/Share/Advanced/NCShareNewUserAddComment.swift @@ -35,17 +35,26 @@ class NCShareNewUserAddComment: UIViewController, NCShareNavigationTitleSetting @IBOutlet weak var btnSendShare: UIButton! @IBOutlet weak var buttonContainerView: UIView! let contentInsets: CGFloat = 16 - public var share: NCTableShareable! + public var share: Shareable! public var metadata: tableMetadata! - var isNewShare: Bool { share is NCTableShareOptions } + var isNewShare: Bool { share is TransientShare } var networking: NCShareNetworking? + /// + /// The possible download limit associated with this share. + /// + /// This can only be created after the share has been actually created due to its requirement of the share token provided by the server. + /// + var downloadLimit: DownloadLimitViewModel = .unlimited + override func viewDidLoad() { super.viewDidLoad() self.setNavigationTitle() NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) +// NotificationCenter.default.addObserver(self, selector: #selector(handleShareCountsUpdate), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterShareCountsUpdated), object: nil) + changeTheming() setupHeader() } @@ -91,6 +100,50 @@ class NCShareNewUserAddComment: UIViewController, NCShareNavigationTitleSetting headerView.rightAnchor.constraint(equalTo: headerContainerView.rightAnchor).isActive = true headerView.setupUI(with: metadata) } + +// @objc private func handleShareCountsUpdate(notification: Notification) { +// guard let userInfo = notification.userInfo, +// let links = userInfo["links"] as? Int, +// let emails = userInfo["emails"] as? Int else { +// return +// } +// +// updateHeader(linkCount: links, emailCount: emails) +// } +// +// private func updateHeader(linkCount: Int, emailCount: Int) { +// // If header already exists, just update it +// if let header = headerContainerView.subviews.first(where: { $0 is NCShareAdvancePermissionHeader }) as? NCShareAdvancePermissionHeader { +// header.setupUI(with: metadata, linkCount: linkCount, emailCount: emailCount) +// } else { +// // Otherwise create and attach +// setupHeader(linkCount: linkCount, emailCount: emailCount) +// } +// } +// +// private func setupHeader(linkCount: Int = 0, emailCount: Int = 0) { +// // Clear old header if any +// headerContainerView.subviews.forEach { $0.removeFromSuperview() } +// +// guard let headerView = Bundle.main.loadNibNamed("NCShareAdvancePermissionHeader", owner: self, options: nil)? +// .first as? NCShareAdvancePermissionHeader else { +// return +// } +// +// headerView.ocId = metadata.ocId +// headerContainerView.addSubview(headerView) +// +// headerView.translatesAutoresizingMaskIntoConstraints = false +// NSLayoutConstraint.activate([ +// headerView.topAnchor.constraint(equalTo: headerContainerView.topAnchor), +// headerView.bottomAnchor.constraint(equalTo: headerContainerView.bottomAnchor), +// headerView.leadingAnchor.constraint(equalTo: headerContainerView.leadingAnchor), +// headerView.trailingAnchor.constraint(equalTo: headerContainerView.trailingAnchor) +// ]) +// +// // Initial setup +// headerView.setupUI(with: metadata, linkCount: linkCount, emailCount: emailCount) +// } @IBAction func cancelClicked(_ sender: Any) { self.navigationController?.popToRootViewController(animated: true) @@ -99,9 +152,9 @@ class NCShareNewUserAddComment: UIViewController, NCShareNavigationTitleSetting @IBAction func sendShareClicked(_ sender: Any) { share.note = commentTextView.text if isNewShare { - networking?.createShare(option: share) + networking?.createShare(share, downloadLimit: downloadLimit) } else { - networking?.updateShare(option: share) + networking?.updateShare(share, downloadLimit: downloadLimit) } self.navigationController?.popToRootViewController(animated: true) } diff --git a/iOSClient/Share/Advanced/NCShareTextInputCell.swift b/iOSClient/Share/Advanced/NCShareTextInputCell.swift index 26da27ef03..a45ce889ce 100644 --- a/iOSClient/Share/Advanced/NCShareTextInputCell.swift +++ b/iOSClient/Share/Advanced/NCShareTextInputCell.swift @@ -14,128 +14,143 @@ class NCShareTextInputCell: XLFormBaseCell, UITextFieldDelegate { @IBOutlet weak var seperatorBottom: UIView! @IBOutlet weak var cellTextField: UITextField! @IBOutlet weak var calendarImageView: UIImageView! - + let datePicker = UIDatePicker() var expirationDateText: String! var expirationDate: NSDate! - + override func awakeFromNib() { super.awakeFromNib() self.cellTextField.delegate = self self.cellTextField.isEnabled = true - calendarImageView.image = UIImage(named: "calender")?.imageColor(NCBrandColor.shared.brandElement) + calendarImageView.image = UIImage(named: "calender")?.imageColor(NCBrandColor.shared.brandElement) self.selectionStyle = .none self.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground - self.cellTextField.attributedPlaceholder = NSAttributedString(string: "", - attributes: [NSAttributedString.Key.foregroundColor: NCBrandColor.shared.fileFolderName]) + self.cellTextField.attributedPlaceholder = NSAttributedString( + string: "", + attributes: [NSAttributedString.Key.foregroundColor: NCBrandColor.shared.fileFolderName] + ) self.cellTextField.textColor = NCBrandColor.shared.singleTitleColorButton } - + override func configure() { super.configure() } - + override func update() { super.update() + calendarImageView.isHidden = rowDescriptor.tag != "NCShareTextInputCellExpiry" + if rowDescriptor.tag == "NCShareTextInputCellExpiry" { seperator.isHidden = true setDatePicker(sender: self.cellTextField) } else if rowDescriptor.tag == "NCShareTextInputCellDownloadLimit" { seperator.isHidden = true cellTextField.keyboardType = .numberPad - setDoneButton(sender: self.cellTextField) + setDownloadLimitDoneButton(sender: self.cellTextField) } } - + + // MARK: - TextField Delegate + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - if self.cellTextField == textField { - if let rowDescriptor = rowDescriptor, let text = self.cellTextField.text { - - if (text + " ").isEmpty == false { - rowDescriptor.value = self.cellTextField.text! + string - } else { - rowDescriptor.value = nil - } - } + if self.cellTextField == textField, let text = self.cellTextField.text { + rowDescriptor.value = (text + string).isEmpty ? nil : text + string } - self.formViewController().textField(textField, shouldChangeCharactersIn: range, replacementString: string) return true } - + func textFieldDidEndEditing(_ textField: UITextField) { rowDescriptor.value = cellTextField.text } - + func textFieldShouldReturn(_ textField: UITextField) -> Bool { self.formViewController()?.textFieldShouldReturn(textField) return true } - + func textFieldShouldClear(_ textField: UITextField) -> Bool { self.formViewController()?.textFieldShouldClear(textField) return true } - + override class func formDescriptorCellHeight(for rowDescriptor: XLFormRowDescriptor!) -> CGFloat { return 30 } - + override func formDescriptorCellDidSelected(withForm controller: XLFormViewController!) { self.selectionStyle = .none } - + + // MARK: - Date Picker + func setDatePicker(sender: UITextField) { - //Format Date datePicker.datePickerMode = .date datePicker.minimumDate = Date.tomorrow if #available(iOS 13.4, *) { datePicker.preferredDatePickerStyle = .wheels datePicker.sizeToFit() } - //ToolBar - let toolbar = UIToolbar(); + + let toolbar = UIToolbar() toolbar.sizeToFit() - let doneButton = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: .plain, target: self, action: #selector(doneDatePicker)); - let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil) - let cancelButton = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: .plain, target: self, action: #selector(cancelDatePicker)); - toolbar.setItems([cancelButton, spaceButton, doneButton], animated: false) + let doneButton = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: .plain, target: self, action: #selector(doneDatePicker)) + let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + let clearButton = UIBarButtonItem(title: NSLocalizedString("_clear_", comment: ""), style: .plain, target: self, action: #selector(clearDatePicker)) + toolbar.setItems([clearButton, spaceButton, doneButton], animated: false) sender.inputAccessoryView = toolbar sender.inputView = datePicker } - + @objc func doneDatePicker() { - let dateFormatter = DateFormatter() - dateFormatter.formatterBehavior = .behavior10_4 - dateFormatter.dateStyle = .medium - dateFormatter.dateFormat = NCShareAdvancePermission.displayDateFormat - - var expiryDate = dateFormatter.string(from: datePicker.date) - expiryDate = expiryDate.replacingOccurrences(of: "..", with: ".") - self.expirationDateText = expiryDate - +// let dateFormatter = DateFormatter() +// dateFormatter.dateFormat = NCShareAdvancePermission.displayDateFormat +// var expiryDate = dateFormatter.string(from: datePicker.date) +// expiryDate = expiryDate.replacingOccurrences(of: "..", with: ".") +// self.expirationDateText = expiryDate +// self.expirationDate = datePicker.date as NSDate + + let expiryDateString = DateFormatter.shareExpDate.string(from: datePicker.date) + self.expirationDateText = expiryDateString self.expirationDate = datePicker.date as NSDate + self.cellTextField.text = self.expirationDateText self.rowDescriptor.value = self.expirationDate self.cellTextField.endEditing(true) } - @objc func cancelDatePicker() { + @objc func clearDatePicker() { + self.expirationDate = nil + self.cellTextField.text = "" + self.rowDescriptor.value = nil self.cellTextField.endEditing(true) } - - func setDoneButton(sender: UITextField) { - //ToolBar - let toolbar = UIToolbar(); + + // MARK: - Download Limit + + func setDownloadLimitDoneButton(sender: UITextField) { + let toolbar = UIToolbar() toolbar.sizeToFit() - let doneButton = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: .plain, target: self, action: #selector(cancelDatePicker)); - let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil) + + let doneButton = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: .plain, target: self, action: #selector(doneDownloadLimit)) + let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) toolbar.setItems([spaceButton, doneButton], animated: false) + sender.inputAccessoryView = toolbar } + + @objc func doneDownloadLimit() { + if let text = cellTextField.text, let limit = Int(text) { + rowDescriptor.value = limit + } else { + rowDescriptor.value = 0 + } + cellTextField.endEditing(true) + } } class NCSeparatorCell: XLFormBaseCell { @@ -143,9 +158,9 @@ class NCSeparatorCell: XLFormBaseCell { super.awakeFromNib() selectionStyle = .none } - + override func update() { super.update() - self.selectionStyle = .none + selectionStyle = .none } } diff --git a/iOSClient/Share/CreateLinkFooterView.swift b/iOSClient/Share/CreateLinkFooterView.swift new file mode 100644 index 0000000000..71e198fe64 --- /dev/null +++ b/iOSClient/Share/CreateLinkFooterView.swift @@ -0,0 +1,79 @@ +// +// CreateLinkFooterView.swift +// Nextcloud +// +// Created by A106551118 on 12/08/25. +// Copyright © 2025 Marino Faggiana. All rights reserved. +// + + +import UIKit + +class CreateLinkFooterView: UITableViewHeaderFooterView { + + static let reuseIdentifier = "CreateLinkFooterView" + + private let createButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle(NSLocalizedString("_create_new_link_", comment: ""), for: .normal) + button.setTitleColor(UIColor.label, for: .normal) +// button.setTitleColor(NCBrandColor.shared.shareBlackColor, for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold) + button.layer.cornerRadius = 7 + button.layer.borderWidth = 1 + button.layer.borderColor = NCBrandColor.shared.label.cgColor + button.backgroundColor = .clear + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let separator: UIView = { + let view = UIView() + view.backgroundColor = .lightGray + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + var createButtonAction: (() -> Void)? + + override init(reuseIdentifier: String?) { + super.init(reuseIdentifier: reuseIdentifier) + setupUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupUI() + } + + override func layoutSubviews() { + super.layoutSubviews() + createButton.layer.borderColor = UIColor.label.cgColor + } + + private func setupUI() { + contentView.backgroundColor = .clear + + contentView.addSubview(createButton) + contentView.addSubview(separator) + + NSLayoutConstraint.activate([ + createButton.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20), + createButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15), + createButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15), + createButton.heightAnchor.constraint(equalToConstant: 40), + + separator.topAnchor.constraint(equalTo: createButton.bottomAnchor, constant: 20), + separator.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + separator.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + separator.heightAnchor.constraint(equalToConstant: 1), + separator.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) // ensures correct height + ]) + + createButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) + } + + @objc private func buttonTapped() { + createButtonAction?() + } +} diff --git a/iOSClient/Share/NCPermissions.swift b/iOSClient/Share/NCPermissions.swift new file mode 100644 index 0000000000..69e84c83e1 --- /dev/null +++ b/iOSClient/Share/NCPermissions.swift @@ -0,0 +1,103 @@ +// +// NCPermissions.swift +// Nextcloud +// +// Created by Marino Faggiana on 05/06/24. +// Copyright © 2024 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +import UIKit +import Foundation + +class NCPermissions: NSObject { + let permissionShared = "S" + let permissionCanShare = "R" + let permissionMounted = "M" + let permissionFileCanWrite = "W" + let permissionCanCreateFile = "C" + let permissionCanCreateFolder = "K" + let permissionCanDelete = "D" + let permissionCanRename = "N" + let permissionCanMove = "V" + + // Share permission + // permissions - (int) 1 = read; 2 = update; 4 = create; 8 = delete; 16 = share; 31 = all + // + @objc let permissionReadShare: Int = 1 + @objc let permissionUpdateShare: Int = 2 + @objc let permissionCreateShare: Int = 4 + @objc let permissionDeleteShare: Int = 8 + @objc let permissionShareShare: Int = 16 + // + let permissionMinFileShare: Int = 1 + let permissionMaxFileShare: Int = 19 + let permissionMinFolderShare: Int = 1 + let permissionMaxFolderShare: Int = 31 + let permissionDefaultFileRemoteShareNoSupportShareOption: Int = 3 + let permissionDefaultFolderRemoteShareNoSupportShareOption: Int = 15 + // 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 isPermissionToCanShare(_ permission: Int) -> Bool { + return ((permission & permissionShareShare) > 0) + } + func isAnyPermissionToEdit(_ permission: Int) -> Bool { + let canCreate = isPermissionToCanCreate(permission) + let canChange = isPermissionToCanChange(permission) + let canDelete = isPermissionToCanDelete(permission) + return canCreate || canChange || canDelete + } + func isPermissionToReadCreateUpdate(_ permission: Int) -> Bool { + let canRead = isPermissionToRead(permission) + let canCreate = isPermissionToCanCreate(permission) + let canChange = isPermissionToCanChange(permission) + return canCreate && canChange && canRead + } + func getPermission(canEdit: Bool, canCreate: Bool, canChange: Bool, canDelete: Bool, canShare: Bool, isDirectory: Bool) -> Int { + var permission = permissionReadShare + + if canEdit && !isDirectory { + permission = permission + permissionUpdateShare + } + if canCreate && isDirectory { + permission = permission + permissionCreateShare + } + if canChange && isDirectory { + permission = permission + permissionUpdateShare + } + if canDelete && isDirectory { + permission = permission + permissionDeleteShare + } + if canShare { + permission = permission + permissionShareShare + } + return permission + } +} diff --git a/iOSClient/Share/NCSearchUserDropDownCell.xib b/iOSClient/Share/NCSearchUserDropDownCell.xib index 6c2ffde821..f476cfafe9 100755 --- a/iOSClient/Share/NCSearchUserDropDownCell.xib +++ b/iOSClient/Share/NCSearchUserDropDownCell.xib @@ -1,9 +1,9 @@ - + - + @@ -73,12 +73,12 @@ - + - + diff --git a/iOSClient/Share/NCShare+NCCellDelegate.swift b/iOSClient/Share/NCShare+NCCellDelegate.swift index 9e8be4757c..412510ed5b 100644 --- a/iOSClient/Share/NCShare+NCCellDelegate.swift +++ b/iOSClient/Share/NCShare+NCCellDelegate.swift @@ -29,10 +29,11 @@ extension NCShare: NCShareLinkCellDelegate, NCShareUserCellDelegate { func copyInternalLink(sender: Any) { guard let metadata = self.metadata else { return } - NCNetworking.shared.readFile(serverUrlFileName: metadata.serverUrlFileName, account: metadata.account) { _, metadata, _, error in + let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName + NCNetworking.shared.readFile(serverUrlFileName: serverUrlFileName, account: metadata.account) { _, metadata, error in if error == .success, let metadata = metadata { let internalLink = metadata.urlBase + "/index.php/f/" + metadata.fileId - NCShareCommon.copyLink(link: internalLink, viewController: self, sender: sender) + self.shareCommon.copyLink(link: internalLink, viewController: self, sender: sender) } else { NCContentPresenter().showError(error: error) } @@ -43,12 +44,11 @@ extension NCShare: NCShareLinkCellDelegate, NCShareUserCellDelegate { guard let tableShare = tableShare else { return copyInternalLink(sender: sender) } - NCShareCommon.copyLink(link: tableShare.url, viewController: self, sender: sender) + shareCommon.copyLink(link: tableShare.url, viewController: self, sender: sender) } func tapMenu(with tableShare: tableShare?, sender: Any) { if let tableShare = tableShare { -// self.toggleShareMenu(for: tableShare, sender: sender) self.toggleShareMenu(for: tableShare, sendMail: (tableShare.shareType != NCShareCommon().SHARE_TYPE_LINK), folder: metadata?.directory ?? false, sender: sender) } else { self.makeNewLinkShare() @@ -57,14 +57,12 @@ extension NCShare: NCShareLinkCellDelegate, NCShareUserCellDelegate { func showProfile(with tableShare: tableShare?, sender: Any) { guard let tableShare else { return } - showProfileMenu(userId: tableShare.shareWith, session: session, sender: sender) + showProfileMenu(userId: tableShare.shareWith, session: session) } func quickStatus(with tableShare: tableShare?, sender: Any) { - guard let tableShare, let metadata else { return } - self.toggleQuickPermissionsMenu(isDirectory: metadata.directory, share: tableShare, sender: sender) -// guard let tableShare = tableShare, -// let metadata = metadata else { return } -// self.toggleUserPermissionMenu(isDirectory: metadata.directory, tableShare: tableShare) + guard let tableShare = tableShare, + let metadata = metadata else { return } + self.toggleUserPermissionMenu(isDirectory: metadata.directory, tableShare: tableShare) } } diff --git a/iOSClient/Share/NCShare.storyboard b/iOSClient/Share/NCShare.storyboard index 85c52dc81f..0c8a27be32 100644 --- a/iOSClient/Share/NCShare.storyboard +++ b/iOSClient/Share/NCShare.storyboard @@ -1,10 +1,9 @@ - + - - + @@ -238,81 +237,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/iOSClient/Share/NCShare.swift b/iOSClient/Share/NCShare.swift index 1b44bd145d..ca94bdf118 100644 --- a/iOSClient/Share/NCShare.swift +++ b/iOSClient/Share/NCShare.swift @@ -30,7 +30,14 @@ import NextcloudKit import MarqueeLabel import ContactsUI -class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent { +enum ShareSection: Int, CaseIterable { + case header + case linkByEmail + case links + case emails +} + +class NCShare: UIViewController, NCSharePagingContent { var textField: UITextField? { self.view.viewWithTag(Tag.searchField) as? UITextField } @@ -39,13 +46,13 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, 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 { guard let metadata = metadata else { return true } return ((metadata.sharePermissionsCollaborationServices & NCPermissions().permissionShareShare) != 0) @@ -57,11 +64,27 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent var shares: (firstShareLink: tableShare?, share: [tableShare]?) = (nil, nil) - var capabilities = NKCapabilities.Capabilities() - private var dropDown = DropDown() var networking: NCShareNetworking? + var isCurrentUser: Bool { + if let currentUser = NCManageDatabase.shared.getActiveTableAccount(), currentUser.userId == metadata?.ownerId { + return true + } + return false + } + var shareLinks: [tableShare] = [] + var shareEmails: [tableShare] = [] + var shareOthers: [tableShare] = [] + private var cachedHeader: NCShareAdvancePermissionHeader? + // Stores the next number per share +// var nextLinkNumberByShare: [String: Int] = [:] +// +// // Stores assigned numbers for each link (per share) +// var linkNumbersByShare: [String: [String: Int]] = [:] + +// var shareLinksCount = 0 + // MARK: - View Life Cycle override func viewDidLoad() { @@ -77,50 +100,40 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent tableView.register(UINib(nibName: "NCShareLinkCell", bundle: nil), forCellReuseIdentifier: "cellLink") tableView.register(UINib(nibName: "NCShareUserCell", bundle: nil), forCellReuseIdentifier: "cellUser") tableView.register(UINib(nibName: "NCShareEmailFieldCell", bundle: nil), forCellReuseIdentifier: "NCShareEmailFieldCell") + tableView.register(NCShareEmailLinkHeaderView.self, + forHeaderFooterViewReuseIdentifier: NCShareEmailLinkHeaderView.reuseIdentifier) + tableView.register(CreateLinkFooterView.self, forHeaderFooterViewReuseIdentifier: CreateLinkFooterView.reuseIdentifier) + tableView.register(NoSharesFooterView.self, forHeaderFooterViewReuseIdentifier: NoSharesFooterView.reuseIdentifier) + tableView.register(UINib(nibName: "NCShareAdvancePermissionHeader", bundle: nil), + forHeaderFooterViewReuseIdentifier: NCShareAdvancePermissionHeader.reuseIdentifier) NotificationCenter.default.addObserver(self, selector: #selector(reloadData), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterReloadDataNCShare), object: nil) - - Task { - self.capabilities = await NKCapabilities.shared.getCapabilities(for: metadata.account) - if metadata.e2eEncrypted { - let metadataDirectory = await self.database.getMetadataDirectoryAsync(serverUrl: metadata.serverUrl, account: metadata.account) - if capabilities.e2EEApiVersion == NCGlobal.shared.e2eeVersionV12 || - (capabilities.e2EEApiVersion == NCGlobal.shared.e2eeVersionV20 && metadataDirectory?.e2eEncrypted ?? false) { - searchFieldTopConstraint.constant = -50 - searchField.alpha = 0 - btnContact.alpha = 0 - } - } else { - checkSharedWithYou() - } - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(reloadData), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDidCreateShareLink), object: nil) + guard let metadata = metadata else { return } +// loadLinkNumberData() reloadData() - networking = NCShareNetworking(metadata: metadata, view: self.view, delegate: self, session: session) + 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) - -// searchField.searchTextField.font = .systemFont(ofSize: 14) -// searchField.delegate = self -// if sharingEnabled { -// let isVisible = (self.navigationController?.topViewController as? NCSharePaging)?.page == .sharing -// networking?.readShare(showLoadingIndicator: isVisible) -// } + } self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: .done, target: self, action: #selector(exitTapped)) navigationItem.largeTitleDisplayMode = .never } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) - tableView.reloadData() +// tableView.reloadData() } @objc func exitTapped() { + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterUpdateIcons) self.dismiss(animated: true, completion: nil) } @@ -128,7 +141,7 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent guard let advancePermission = UIStoryboard(name: "NCShare", bundle: nil).instantiateViewController(withIdentifier: "NCShareAdvancePermission") as? NCShareAdvancePermission, let navigationController = self.navigationController else { return } - self.checkEnforcedPassword(shareType: NCShareCommon.shareTypeLink) { password in + self.checkEnforcedPassword(shareType: shareCommon.SHARE_TYPE_LINK) { password in advancePermission.networking = self.networking advancePermission.share = TransientShare.shareLink(metadata: self.metadata, password: password) advancePermission.metadata = self.metadata @@ -136,103 +149,53 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent } } - // Shared with you by ... - func checkSharedWithYou() { - guard !metadata.ownerId.isEmpty, metadata.ownerId != session.userId else { return } - - if !canReshare { - searchField.isUserInteractionEnabled = false - searchField.alpha = 0.5 - searchField.placeholder = NSLocalizedString("_share_reshare_disabled_", comment: "") - btnContact.isEnabled = false - } - - searchFieldTopConstraint.constant = 45 - sharedWithYouByView.isHidden = false - sharedWithYouByLabel.text = NSLocalizedString("_shared_with_you_by_", comment: "") + " " + metadata.ownerDisplayName - 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) - - let fileName = NCSession.shared.getFileName(urlBase: session.urlBase, user: metadata.ownerId) - let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) - - if results.image == nil { - let etag = self.database.getTableAvatar(fileName: fileName)?.etag - let fileNameLocalPath = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName) - - NextcloudKit.shared.downloadAvatar( - user: metadata.ownerId, - fileNameLocalPath: fileNameLocalPath, - sizeImage: NCGlobal.shared.avatarSize, - avatarSizeRounded: NCGlobal.shared.avatarSizeRounded, - etagResource: etag, - account: metadata.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.metadata.account, - path: self.metadata.ownerId, - name: "downloadAvatar") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { _, imageAvatar, _, etag, _, error in - 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 - @objc func openShareProfile(_ sender: UITapGestureRecognizer) { - self.showProfileMenu(userId: metadata.ownerId, session: session, sender: sender.view) - } - @objc func openShareProfile() { guard let metadata = metadata else { return } self.showProfileMenu(userId: metadata.ownerId, session: session) } + private func scrollToTopIfNeeded() { + if tableView.numberOfSections > 0 && tableView.numberOfRows(inSection: 0) > 0 { + self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + } + } + @objc func keyboardWillShow(notification: Notification) { if UIDevice.current.userInterfaceIdiom == .phone { - if (UIScreen.main.bounds.width < 374 || UIDevice.current.orientation.isLandscape) { + if UIScreen.main.bounds.width < 374 || UIDevice.current.orientation.isLandscape { if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { if view.frame.origin.y == 0 { - self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + scrollToTopIfNeeded() self.view.frame.origin.y -= keyboardSize.height } } } else if UIScreen.main.bounds.height < 850 { if view.frame.origin.y == 0 { - self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + scrollToTopIfNeeded() self.view.frame.origin.y -= 70 } } else { if view.frame.origin.y == 0 { - self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + scrollToTopIfNeeded() self.view.frame.origin.y -= 40 } } } - + if UIDevice.current.userInterfaceIdiom == .pad, UIDevice.current.orientation.isLandscape { if view.frame.origin.y == 0 { - self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + if tableView.numberOfSections > 0 && tableView.numberOfRows(inSection: 0) > 0 { + self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) + } self.view.frame.origin.y -= 230 } } + textField?.layer.borderColor = NCBrandColor.shared.brand.cgColor } + @objc func keyboardWillHide(notification: Notification) { if view.frame.origin.y != 0 { @@ -244,22 +207,293 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent @objc func appWillEnterForeground(notification: Notification) { reloadData() } + +// @objc private func handleShareCountsUpdate(notification: Notification) { +// guard let userInfo = notification.userInfo, +// let links = userInfo["links"] as? Int, +// let emails = userInfo["emails"] as? Int else { return } +// +// if let header = tableView.headerView(forSection: ShareSection.header.rawValue) as? NCShareAdvancePermissionHeader { +// header.setupUI(with: metadata, +// linkCount: links, +// emailCount: emails) +// } else { +// let headerSection = IndexSet(integer: ShareSection.header.rawValue) +// tableView.reloadSections(headerSection, with: .none) +// } +// } + // MARK: - @objc func reloadData() { + guard let metadata = metadata else { + return + } +// shares = (nil, nil) // reset shares = self.database.getTableShares(metadata: metadata) - shareLinksCount = 0 -// if let metadata = metadata { -// shares = self.database.getTableShares(metadata: metadata) -// } + updateShareArrays() tableView.reloadData() } + + func updateShareArrays() { + shareLinks.removeAll() + shareEmails.removeAll() + + guard var allShares = shares.share else { return } + + if let firstLink = shares.firstShareLink { + // Remove if already exists to avoid duplication + allShares.removeAll { $0.idShare == firstLink.idShare } + allShares.insert(firstLink, at: 0) + } + + shares.share = allShares + + for item in allShares { + if item.shareType == shareCommon.SHARE_TYPE_LINK { + shareLinks.append(item) + } else { + shareEmails.append(item) + } + } + } + + +// func updateShareArrays() { +// shareLinks.removeAll() +// shareEmails.removeAll() +// +// var allShares = shares.share ?? [] +// +// if let firstLink = shares.firstShareLink { +// if let idx = allShares.firstIndex(where: { $0.idShare == firstLink.idShare }) { +// allShares.remove(at: idx) // only one removal +// } +// allShares.insert(firstLink, at: 0) +// } +// +// shares.share = allShares +// +// for item in allShares { +// if item.shareType == shareCommon.SHARE_TYPE_LINK { +// shareLinks.append(item) +// } else { +// shareEmails.append(item) +// } +// } +// } + + + + +// func updateShareArrays() { +// shareLinks.removeAll() +// shareEmails.removeAll() +// +// if let shareLink = shares.firstShareLink { +// shares.share?.insert(shareLink, at: 0) +// } +// +// guard let allShares = shares.share else { return } +// +//// // Use current shareId as the scope +//// let shareId = metadata?.ocId ?? "0" +//// +//// // Ensure storage exists for this share +//// if nextLinkNumberByShare[shareId] == nil { +//// nextLinkNumberByShare[shareId] = 1 +//// linkNumbersByShare[shareId] = [:] +//// } +// +// for item in allShares { +// if item.shareType == shareCommon.SHARE_TYPE_LINK { +// shareLinks.append(item) +// } else { +// shareEmails.append(item) +// } +// } +// +//// // Sort links by assigned number (per-share) +//// shareLinks.sort { lhs, rhs in +//// let lhsNum = linkNumbersByShare[shareId]?[String(lhs.idShare)] ?? 0 +//// let rhsNum = linkNumbersByShare[shareId]?[String(rhs.idShare)] ?? 0 +//// return lhsNum < rhsNum +//// } +//// +//// // ✅ If this share has no links, reset numbering for it +//// if shareLinks.isEmpty { +//// linkNumbersByShare[shareId] = [:] +//// nextLinkNumberByShare[shareId] = 1 +//// saveLinkNumberData() +//// } +// +//// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare, +//// userInfo: ["links": shareLinks.count, +//// "emails": shareEmails.count]) +// } + +// // MARK: - Persistence +// func saveLinkNumberData() { +// // Save both maps as UserDefaults property lists +// UserDefaults.standard.set(linkNumbersByShare, forKey: "linkNumbersByShare") +// UserDefaults.standard.set(nextLinkNumberByShare, forKey: "nextLinkNumberByShare") +// } +// +// func loadLinkNumberData() { +// if let savedMap = UserDefaults.standard.dictionary(forKey: "linkNumbersByShare") as? [String: [String: Int]] { +// linkNumbersByShare = savedMap +// } else { +// linkNumbersByShare = [:] +// } +// +// if let savedNext = UserDefaults.standard.dictionary(forKey: "nextLinkNumberByShare") as? [String: Int] { +// nextLinkNumberByShare = savedNext +// } else { +// nextLinkNumberByShare = [:] +// } +// } +// +// // MARK: - Number Assignment +// +// // Assign number to a link (or reuse existing) +// func assignLinkNumber(forShare shareId: String, linkId: String) -> Int { +// if nextLinkNumberByShare[shareId] == nil { +// nextLinkNumberByShare[shareId] = 1 +// linkNumbersByShare[shareId] = [:] +// } +// +// if let number = linkNumbersByShare[shareId]?[linkId] { +// return number +// } +// +// let nextNum = nextLinkNumberByShare[shareId]! +// linkNumbersByShare[shareId]?[linkId] = nextNum +// nextLinkNumberByShare[shareId]! += 1 +// return nextNum +// } +// +// func removeLink(forShare shareId: String, linkId: String) { +// linkNumbersByShare[shareId]?.removeValue(forKey: linkId) +// +// if linkNumbersByShare[shareId]?.isEmpty ?? true { +// linkNumbersByShare[shareId] = [:] +// nextLinkNumberByShare[shareId] = 1 +// } +// } + + + +// func updateShareArrays() { +// shareLinks.removeAll() +// shareEmails.removeAll() +// +// if let shareLink = shares.firstShareLink { +// shares.share?.insert(shareLink, at: 0) +// } +// +// guard let allShares = shares.share else { return } +// +// // Use current shareId as the scope +// let shareId = metadata?.ocId ?? "0" +// +// // Ensure storage exists for this share +// if nextLinkNumberByShare[shareId] == nil { +// nextLinkNumberByShare[shareId] = 1 +// linkNumbersByShare[shareId] = [:] +// } +// +// for item in allShares { +// if item.shareType == shareCommon.SHARE_TYPE_LINK { +// let linkId = String(item.idShare) +// +// // Assign a number if missing +// if linkNumbersByShare[shareId]?[linkId] == nil { +// let nextNum = nextLinkNumberByShare[shareId] ?? 1 +// linkNumbersByShare[shareId]?[linkId] = nextNum +// nextLinkNumberByShare[shareId] = nextNum + 1 +// saveLinkNumberData() +// } +//// if item.shareType == shareCommon.SHARE_TYPE_LINK { shareLinksCount += 1 } +// +// shareLinks.append(item) +// } else { +// shareEmails.append(item) +// } +// } +// +// // Sort links by assigned number (per-share) +// shareLinks.sort { lhs, rhs in +// let lhsNum = linkNumbersByShare[shareId]?[String(lhs.idShare)] ?? 0 +// let rhsNum = linkNumbersByShare[shareId]?[String(rhs.idShare)] ?? 0 +// return lhsNum < rhsNum +// } +// +// // ✅ If this share has no links, reset numbering for it +// if shareLinks.isEmpty { +// linkNumbersByShare[shareId] = [:] +// nextLinkNumberByShare[shareId] = 1 +// saveLinkNumberData() +// } +// +//// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare, +//// userInfo: ["links": shareLinks.count, +//// "emails": shareEmails.count]) +// } +// +// // MARK: - Persistence +// func saveLinkNumberData() { +// // Save both maps as UserDefaults property lists +// UserDefaults.standard.set(linkNumbersByShare, forKey: "linkNumbersByShare") +// UserDefaults.standard.set(nextLinkNumberByShare, forKey: "nextLinkNumberByShare") +// } +// +// func loadLinkNumberData() { +// if let savedMap = UserDefaults.standard.dictionary(forKey: "linkNumbersByShare") as? [String: [String: Int]] { +// linkNumbersByShare = savedMap +// } else { +// linkNumbersByShare = [:] +// } +// +// if let savedNext = UserDefaults.standard.dictionary(forKey: "nextLinkNumberByShare") as? [String: Int] { +// nextLinkNumberByShare = savedNext +// } else { +// nextLinkNumberByShare = [:] +// } +// } +// +// // MARK: - Number Assignment +// +// // Assign number to a link (or reuse existing) +// func assignLinkNumber(forShare shareId: String, linkId: String) -> Int { +// if nextLinkNumberByShare[shareId] == nil { +// nextLinkNumberByShare[shareId] = 1 +// linkNumbersByShare[shareId] = [:] +// } +// +// if let number = linkNumbersByShare[shareId]?[linkId] { +// return number +// } +// +// let nextNum = nextLinkNumberByShare[shareId]! +// linkNumbersByShare[shareId]?[linkId] = nextNum +// nextLinkNumberByShare[shareId]! += 1 +// return nextNum +// } +// +// func removeLink(forShare shareId: String, linkId: String) { +// linkNumbersByShare[shareId]?.removeValue(forKey: linkId) +// +// if linkNumbersByShare[shareId]?.isEmpty ?? true { +// linkNumbersByShare[shareId] = [:] +// nextLinkNumberByShare[shareId] = 1 +// } +// } // MARK: - IBAction @IBAction func searchFieldDidEndOnExit(textField: UITextField) { guard let searchString = textField.text, !searchString.isEmpty else { return } - if searchString.contains("@"), !utility.isValidEmail(searchString) { return } + if searchString.contains("@"), !utility.validateEmail(searchString) { return } networking?.getSharees(searchString: searchString) } @@ -274,23 +508,24 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent } } - @objc private func searchSharees() { - // https://stackoverflow.com/questions/25471114/how-to-validate-an-e-mail-address-in-swift - func isValidEmail(_ email: String) -> Bool { - - let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" - let emailPred = NSPredicate(format: "SELF MATCHES %@", emailRegEx) - return emailPred.evaluate(with: email) - } - guard let searchString = textField?.text, !searchString.isEmpty else { return } - if searchString.contains("@"), !isValidEmail(searchString) { return } - networking?.getSharees(searchString: searchString) - } +// @objc private func searchSharees() { +// // https://stackoverflow.com/questions/25471114/how-to-validate-an-e-mail-address-in-swift +// func isValidEmail(_ email: String) -> Bool { +// +// let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" +// let emailPred = NSPredicate(format: "SELF MATCHES %@", emailRegEx) +// return emailPred.evaluate(with: email) +// } +// guard let searchString = textField?.text, !searchString.isEmpty else { return } +// if searchString.contains("@"), !isValidEmail(searchString) { return } +// networking?.getSharees(searchString: searchString) +// } - @IBAction func createLinkClicked(_ sender: Any) { + @IBAction func createLinkClicked(_ sender: Any?) { appDelegate?.adjust.trackEvent(TriggerEvent(CreateLink.rawValue)) TealiumHelper.shared.trackEvent(title: "magentacloud-app.sharing.create", data: ["": ""]) - self.touchUpInsideButtonMenu(sender) +// self.touchUpInsideButtonMenu(sender) + self.touchUpInsideButtonMenu(sender as Any) } @IBAction func touchUpInsideButtonMenu(_ sender: Any) { @@ -320,6 +555,16 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent } } + + private func createShareAndReload(password: String) { + networking?.createShareLink(password: password) + + // Delay to wait for DB update or async API completion + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + self.reloadData() + } + } + @IBAction func selectContactClicked(_ sender: Any) { let cnPicker = CNContactPickerViewController() @@ -332,21 +577,12 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent } func checkEnforcedPassword(shareType: Int, completion: @escaping (String?) -> Void) { - guard capabilities.fileSharingPubPasswdEnforced, - shareType == NCShareCommon.shareTypeLink || shareType == NCShareCommon.shareTypeEmail + guard NCCapabilities.Capabilities().capabilityFileSharingPubPasswdEnforced, + shareType == shareCommon.SHARE_TYPE_LINK || shareType == shareCommon.SHARE_TYPE_EMAIL else { return completion(nil) } self.present(UIAlertController.password(titleKey: "_enforce_password_protection_", completion: completion), animated: true) } - - @IBAction func selectContactClicked(_ sender: Any) { - let cnPicker = CNContactPickerViewController() - cnPicker.delegate = self - cnPicker.displayedPropertyKeys = [CNContactEmailAddressesKey] - cnPicker.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0") - cnPicker.predicateForSelectionOfProperty = NSPredicate(format: "emailAddresses.@count > 0") - self.present(cnPicker, animated: true) - } } // MARK: - NCShareNetworkingDelegate @@ -354,32 +590,37 @@ class NCShare: UIViewController, NCShareNetworkingDelegate, NCSharePagingContent extension NCShare: NCShareNetworkingDelegate { func readShareCompleted() { NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) - reloadData() +// self.reloadData() } func shareCompleted() { - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) - reloadData() +// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) +// self.reloadData() + // Allow DB async save to finish before reload + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + self.reloadData() + } } func unShareCompleted() { -// reloadData() -// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) - self.reloadData() +// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) + // Same buffer for consistency + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + self.reloadData() + } } +// func unShareCompleted() { +// DispatchQueue.main.async { +// self.reloadData() +// } +// } func updateShareWithError(idShare: Int) { - reloadData() + self.reloadData() } func getSharees(sharees: [NKSharee]?) { - guard let sharees else { - return - } - - // close keyboard - self.view.endEditing(true) + guard let sharees else { return } dropDown = DropDown() let appearance = DropDown.appearance() @@ -393,18 +634,11 @@ extension NCShare: NCShareNetworkingDelegate { appearance.textColor = .darkGray appearance.setupMaskedCorners([.layerMaxXMaxYCorner, .layerMinXMaxYCorner]) - 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 == NCShareCommon.shareTypeTeam { + if sharee.shareType == shareCommon.SHARE_TYPE_CIRCLE { label += " (\(sharee.circleInfo), \(sharee.circleOwner))" } - dropDown.dataSource.append(label) } @@ -472,9 +706,25 @@ extension NCShare: NCShareNetworkingDelegate { extension NCShare: UITableViewDelegate { func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { -// return 60 - return indexPath.row == 0 ? UITableView.automaticDimension : 60 + guard let sectionType = ShareSection(rawValue: indexPath.section) else { return 0 } + + switch sectionType { + case .header: + return 210 + + case .linkByEmail: + let isPad = UIDevice.current.userInterfaceIdiom == .pad + if isCurrentUser { + return 130 + } else { + return isPad ? (canReshare ? 200 : 220) : 220 + } + + case .links, .emails: + return 60 + } } + } // MARK: - UITableViewDataSource @@ -482,172 +732,156 @@ extension NCShare: UITableViewDelegate { extension NCShare: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { - return 1 + ShareSection.allCases.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - var numRows = shares.share?.count ?? 0 - if section == 0 { - if metadata.e2eEncrypted, capabilities.e2EEApiVersion == NCGlobal.shared.e2eeVersionV12 { - numRows = 1 - } else { - // don't allow link creation if reshare is disabled - numRows = shares.firstShareLink != nil || canReshare ? 2 : 1 - } - } - if let shareLink = shares.firstShareLink { - shares.share?.insert(shareLink, at: 0) + guard let sectionType = ShareSection(rawValue: section) else { return 0 } + + switch sectionType { + case .header: + return 0 + case .linkByEmail: + return 1 + case .links: + return shareLinks.count + case .emails: + return shareEmails.count } - - if shares.share != nil { - numOfRows = shares.share!.count - } - return canReshare ? (numOfRows + 1) : numOfRows } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - - if indexPath.row == 0 { - guard let cell = tableView.dequeueReusableCell(withIdentifier: "NCShareEmailFieldCell", for: indexPath) as? NCShareEmailFieldCell else { return UITableViewCell() } + guard let sectionType = ShareSection(rawValue: indexPath.section) else { return UITableViewCell() } + + switch sectionType { + case .header: + return UITableViewCell() // Empty row + case .linkByEmail: + guard let cell = tableView.dequeueReusableCell(withIdentifier: "NCShareEmailFieldCell", for: indexPath) as? NCShareEmailFieldCell else { + return UITableViewCell() + } cell.searchField.addTarget(self, action: #selector(searchFieldDidEndOnExit(textField:)), for: .editingDidEndOnExit) cell.searchField.addTarget(self, action: #selector(searchFieldDidChange(textField:)), for: .editingChanged) - cell.btnCreateLink.addTarget(self, action: #selector(createLinkClicked(_:)), for: .touchUpInside) cell.btnContact.addTarget(self, action: #selector(selectContactClicked(_:)), for: .touchUpInside) - cell.labelNoShare.isHidden = (shares.share?.count ?? 0) > 0 - cell.heightLabelNoShare.constant = (shares.share?.count ?? 0) > 0 ? 0 : 25 + cell.setupCell(with: metadata) return cell - } - - // Setup default share cells - guard indexPath.section != 0 else { - guard let cell = tableView.dequeueReusableCell(withIdentifier: "cellLink", for: indexPath) as? NCShareLinkCell - else { return UITableViewCell() } + + case .links: + let tableShare = shareLinks[indexPath.row] + guard let cell = tableView.dequeueReusableCell(withIdentifier: "cellLink", for: indexPath) as? NCShareLinkCell else { + return UITableViewCell() + } cell.delegate = self - if metadata.e2eEncrypted, capabilities.e2EEApiVersion == NCGlobal.shared.e2eeVersionV12 { - cell.tableShare = shares.firstShareLink + if indexPath.row == 0 { + cell.configure(with: tableShare, at: indexPath, isDirectory: metadata.directory, title: "") } else { - if indexPath.row == 0 { - cell.isInternalLink = true - } else if shares.firstShareLink?.isInvalidated != true { - cell.tableShare = shares.firstShareLink - } + let linkNumber = " \(indexPath.row + 1)" + cell.configure(with: tableShare, at: indexPath, isDirectory: metadata.directory, title: linkNumber) } - cell.isDirectory = metadata.directory - cell.setupCellUI() - shareLinksCount += 1 - - let directory = self.metadata?.directory ?? false - guard let appDelegate = appDelegate, let tableShare = shares.share?[indexPath.row - 1] else { return UITableViewCell() } - - // // 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(titleAppendString: String(shareLinksCount)) - // if tableShare.shareType == shareCommon.SHARE_TYPE_LINK { shareLinksCount += 1 } - // return cell - - // LINK - if tableShare.shareType == shareCommon.SHARE_TYPE_LINK { - guard let cell = tableView.dequeueReusableCell(withIdentifier: "cellLink", for: indexPath) as? NCShareLinkCell - else { return UITableViewCell() } - cell.tableShare = tableShare - cell.delegate = self - cell.setupCellUI() - if !tableShare.label.isEmpty { - cell.labelTitle.text = String(format: NSLocalizedString("_share_linklabel_", comment: ""), tableShare.label) - } else { - cell.labelTitle.text = directory ? NSLocalizedString("_share_link_folder_", comment: "") : NSLocalizedString("_share_link_file_", comment: "") - } -// cell.setupCellUI(userId: session.userId) - let isEditingAllowed = shareCommon.isEditingEnabled(isDirectory: directory, fileExtension: metadata?.fileExtension ?? "", shareType: tableShare.shareType) - if isEditingAllowed || directory || checkIsCollaboraFile() { - cell.btnQuickStatus.isEnabled = true - cell.labelQuickStatus.textColor = NCBrandColor.shared.brand - cell.imageDownArrow.image = UIImage(named: "downArrow")?.imageColor(NCBrandColor.shared.brand) - } else { - cell.btnQuickStatus.isEnabled = false - cell.labelQuickStatus.textColor = NCBrandColor.shared.optionItem - cell.imageDownArrow.image = UIImage(named: "downArrow")?.imageColor(NCBrandColor.shared.optionItem) - } - - return cell - } else { - // USER / GROUP etc. - if let cell = tableView.dequeueReusableCell(withIdentifier: "cellUser", for: indexPath) as? NCShareUserCell { - cell.tableShare = tableShare - cell.isDirectory = metadata.directory - cell.delegate = self - cell.setupCellUI(userId: session.userId, session: session, metadata: metadata) - // cell.setupCellUI(userId: appDelegate.userId) - let isEditingAllowed = shareCommon.isEditingEnabled(isDirectory: directory, fileExtension: metadata?.fileExtension ?? "", shareType: tableShare.shareType) - if isEditingAllowed || checkIsCollaboraFile() { - cell.btnQuickStatus.isEnabled = true - } else { - cell.btnQuickStatus.isEnabled = false - cell.labelQuickStatus.textColor = NCBrandColor.shared.optionItem - cell.imageDownArrow.image = UIImage(named: "downArrow")?.imageColor(NCBrandColor.shared.optionItem) - } - return cell - } + return cell + + case .emails: + let tableShare = shareEmails[indexPath.row] + guard let cell = tableView.dequeueReusableCell(withIdentifier: "cellUser", for: indexPath) as? NCShareUserCell else { + return UITableViewCell() } + cell.delegate = self + cell.configure(with: tableShare, at: indexPath, isDirectory: metadata.directory, userId: session.userId) + return cell } - return UITableViewCell() + } + + func numberOfRows(in section: Int) -> Int { + return tableView(tableView, numberOfRowsInSection: section) } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - guard let headerView = Bundle.main.loadNibNamed("NCShareHeaderView", owner: self, options: nil)?.first as? NCShareHeaderView else { - return UIView() - } - headerView.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground - headerView.fileName.textColor = NCBrandColor.shared.label - headerView.labelSharing.textColor = NCBrandColor.shared.label - headerView.labelSharingInfo.textColor = NCBrandColor.shared.label - headerView.info.textColor = NCBrandColor.shared.systemGray2 - headerView.ocId = metadata!.ocId - headerView.updateCanReshareUI() - - - if let image = NCUtility().getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt1024) { - headerView.fullWidthImageView.image = image -// headerView.fullWidthImageView.image = UIImage(contentsOfFile: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata?.ocId ?? "", etag: metadata?.etag ?? "")) - headerView.fullWidthImageView.contentMode = .scaleAspectFill - headerView.imageView.isHidden = true - } else { - if metadata?.directory ?? false { - let image = (metadata?.e2eEncrypted ?? false) ? NCImageCache.shared.getFolderEncrypted() : NCImageCache.shared.getFolder() - headerView.imageView.image = image - } else if !(metadata?.iconName.isEmpty ?? false) { - if let image = UIImage.init(named: metadata!.iconName) { - headerView.imageView.image = metadata!.fileExtension == "odg" ? UIImage(named: "diagram") : image - } else { - headerView.imageView.image = metadata!.fileExtension == "odg" ? UIImage(named: "diagram") : NCUtility().loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) - } - } else { - headerView.imageView.image = NCImageCache.shared.getImageFile() + guard let sectionType = ShareSection(rawValue: section) else { return nil } + + switch sectionType { + case .header: + let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: NCShareAdvancePermissionHeader.reuseIdentifier) as! NCShareAdvancePermissionHeader + headerView.ocId = metadata.ocId + headerView.setupUI(with: metadata, linkCount: shareLinks.count, emailCount: shareEmails.count) + return headerView + + case .linkByEmail: + return nil + + case .links: + if isCurrentUser || canReshare { + let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "NCShareEmailLinkHeaderView") as! NCShareEmailLinkHeaderView + headerView.configure(text: NSLocalizedString("_share_copy_link_", comment: "")) + return headerView + } + return nil + + case .emails: + if (isCurrentUser || canReshare) && numberOfRows(in: section) > 0 { + let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "NCShareEmailLinkHeaderView") as! NCShareEmailLinkHeaderView + headerView.configure(text: NSLocalizedString("_share_shared_with_", comment: "")) + return headerView } + return nil } - - headerView.fileName.text = metadata?.fileNameView - headerView.fileName.textColor = NCBrandColor.shared.label - if metadata!.favorite { - headerView.favorite.setImage(utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.yellowFavorite], size: 24), for: .normal) - } else { - headerView.favorite.setImage(utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.textInfo], size: 24), for: .normal) + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + guard let sectionType = ShareSection(rawValue: section) else { return 0 } + + switch sectionType { + case .header: + return 190 + case .linkByEmail: + return 0 + case .links: + return (isCurrentUser || canReshare) ? 44 : 0 + case .emails: + return ((isCurrentUser || canReshare) && numberOfRows(in: section) > 0) ? 44 : 0 } - headerView.info.text = utilityFileSystem.transformedSize(metadata!.size) + ", " + utility.dateDiff(metadata!.date as Date) - return headerView - } - func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat { - return metadata?.ownerId != session?.userId ? canReshare ? 400 : 350 : 320 + + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + guard isCurrentUser || canReshare, + let sectionType = ShareSection(rawValue: section) else { + return nil + } + + switch sectionType { + case .links: + let footer = tableView.dequeueReusableHeaderFooterView(withIdentifier: CreateLinkFooterView.reuseIdentifier) as! CreateLinkFooterView + footer.createButtonAction = { [weak self] in + self?.createLinkClicked(nil) + } + return footer + + case .emails: + if numberOfRows(in: section) == 0 { + return tableView.dequeueReusableHeaderFooterView(withIdentifier: NoSharesFooterView.reuseIdentifier) + } + return nil + case .header, .linkByEmail: + return nil + } } - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return metadata?.ownerId != session?.userId ? canReshare ? UITableView.automaticDimension : 350 : 320 + + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + guard isCurrentUser || canReshare, + let sectionType = ShareSection(rawValue: section) else { + return 0.001 + } + + switch sectionType { + case .links: + return 80 + case .emails: + return numberOfRows(in: section) == 0 ? 100 : 80 + case .header, .linkByEmail: + return 0.001 + } } + + } //MARK: CNContactPickerDelegate @@ -655,7 +889,7 @@ extension NCShare: UITableViewDataSource { extension NCShare: CNContactPickerDelegate { func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) { if contact.emailAddresses.count > 1 { - showEmailList(arrEmail: contact.emailAddresses.map({$0.value as String}), sender: picker) + showEmailList(arrEmail: contact.emailAddresses.map({$0.value as String})) } else if let email = contact.emailAddresses.first?.value as? String { textField?.text = email networking?.getSharees(searchString: email) @@ -675,7 +909,6 @@ extension NCShare: CNContactPickerDelegate { icon: utility.loadImage(named: "email").imageColor(NCBrandColor.shared.brandElement), selected: false, on: false, - sender: sender, action: { _ in self.textField?.text = email self.networking?.getSharees(searchString: email) @@ -684,7 +917,7 @@ extension NCShare: CNContactPickerDelegate { ) } DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.presentMenu(with: actions, sender: sender) + self.presentMenu(with: actions) } } } @@ -693,25 +926,27 @@ extension NCShare: CNContactPickerDelegate { extension NCShare: UISearchBarDelegate { func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { - NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(searchSharees(_:)), object: nil) + NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(searchSharees), object: nil) if searchText.isEmpty { dropDown.hide() } else { - perform(#selector(searchSharees(_:)), with: nil, afterDelay: 1) + perform(#selector(searchSharees), with: nil, afterDelay: 0.5) } } - @objc private func searchSharees(_ sender: Any?) { - // https://stackoverflow.com/questions/25471114/how-to-validate-an-e-mail-address-in-swift + @objc private func searchSharees() { +// // https://stackoverflow.com/questions/25471114/how-to-validate-an-e-mail-address-in-swift func isValidEmail(_ email: String) -> Bool { +// let emailRegEx = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-\\u00a1-\\uffff]+\\.[A-Za-z\\u00a1-\\uffff]{2,64}$" let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" let emailPred = NSPredicate(format: "SELF MATCHES %@", emailRegEx) return emailPred.evaluate(with: email) } - guard let searchString = searchField.text, !searchString.isEmpty else { return } - if searchString.contains("@"), !isValidEmail(searchString) { return } + guard let searchString = textField?.text, !searchString.isEmpty else { return } + if searchString.contains("@"), !utility.validateEmail(searchString) { return } networking?.getSharees(searchString: searchString) } + } diff --git a/iOSClient/Share/NCShareCommentsCell.swift b/iOSClient/Share/NCShareCommentsCell.swift index 59cdf78c81..b537f6559d 100644 --- a/iOSClient/Share/NCShareCommentsCell.swift +++ b/iOSClient/Share/NCShareCommentsCell.swift @@ -54,8 +54,9 @@ class NCShareCommentsCell: UITableViewCell, NCCellProtocol { override func awakeFromNib() { super.awakeFromNib() - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapAvatarImage(_:))) + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapAvatarImage)) imageItem?.addGestureRecognizer(tapGesture) + buttonMenu.setImage(UIImage(named: "shareMenu")!.image(color: .lightGray, size: 50), for: .normal) } @objc func tapAvatarImage(_ sender: UITapGestureRecognizer) { diff --git a/iOSClient/Share/NCShareCommon.swift b/iOSClient/Share/NCShareCommon.swift index 999cc27dd9..9b05aea1a1 100644 --- a/iOSClient/Share/NCShareCommon.swift +++ b/iOSClient/Share/NCShareCommon.swift @@ -23,22 +23,23 @@ import UIKit import DropDown -enum NCShareCommon { - static let shareTypeUser = 0 - static let shareTypeGroup = 1 - static let shareTypeLink = 3 - static let shareTypeEmail = 4 - static let shareTypeContact = 5 - static let shareTypeFederated = 6 - static let shareTypeTeam = 7 - static let shareTypeGuest = 8 - static let shareTypeFederatedGroup = 9 - static let shareTypeRoom = 10 +class NCShareCommon: NSObject { - static let itemTypeFile = "file" - static let itemTypeFolder = "folder" + // swiftlint:disable identifier_name + let SHARE_TYPE_USER = 0 + let SHARE_TYPE_GROUP = 1 + let SHARE_TYPE_LINK = 3 + let SHARE_TYPE_EMAIL = 4 + let SHARE_TYPE_CONTACT = 5 + let SHARE_TYPE_REMOTE = 6 + let SHARE_TYPE_CIRCLE = 7 + let SHARE_TYPE_GUEST = 8 + let SHARE_TYPE_REMOTE_GROUP = 9 + let SHARE_TYPE_ROOM = 10 + // swiftlint:enable identifier_name + + func createLinkAvatar(imageName: String, colorCircle: UIColor) -> UIImage? { - static func createLinkAvatar(imageName: String, colorCircle: UIColor) -> UIImage? { let size: CGFloat = 200 let bottomImage = UIImage(named: "circle_fill")!.image(color: colorCircle, size: size / 2) @@ -52,7 +53,7 @@ enum NCShareCommon { return image } - static func copyLink(link: String, viewController: UIViewController, sender: Any) { + func copyLink(link: String, viewController: UIViewController, sender: Any) { let objectsToShare = [link] let activityViewController = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil) @@ -80,19 +81,13 @@ enum NCShareCommon { case self.SHARE_TYPE_EMAIL: return UIImage(named: isDropDown ? "email" : "shareTypeUser")?.imageColor(NCBrandColor.shared.label) case self.SHARE_TYPE_CONTACT: - return UIImage(named: "shareTypeUser")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) - case NCShareCommon.shareTypeFederated: - return UIImage(named: "shareTypeUser")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) -// return UIImage(named: "shareTypeUser")?.imageColor(NCBrandColor.shared.label) + return UIImage(named: "shareTypeUser")?.imageColor(NCBrandColor.shared.label) case self.SHARE_TYPE_REMOTE: return UIImage(named: isDropDown ? "shareTypeUser" : "shareTypeEmail")?.imageColor(NCBrandColor.shared.label) case self.SHARE_TYPE_CIRCLE: return UIImage(named: "shareTypeCircles")?.imageColor(NCBrandColor.shared.label) case self.SHARE_TYPE_GUEST: - return UIImage(named: "shareTypeUser")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) - case NCShareCommon.shareTypeFederatedGroup: - return UIImage(named: "shareTypeGroup")?.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) -// return UIImage(named: "shareTypeUser")?.imageColor(NCBrandColor.shared.label) + return UIImage(named: "shareTypeUser")?.imageColor(NCBrandColor.shared.label) case self.SHARE_TYPE_REMOTE_GROUP: return UIImage(named: "shareTypeGroup")?.imageColor(NCBrandColor.shared.label) case self.SHARE_TYPE_ROOM: diff --git a/iOSClient/Share/NCShareEmailFieldCell.swift b/iOSClient/Share/NCShareEmailFieldCell.swift index c39be3b210..7b8643fe08 100644 --- a/iOSClient/Share/NCShareEmailFieldCell.swift +++ b/iOSClient/Share/NCShareEmailFieldCell.swift @@ -7,83 +7,169 @@ // import UIKit +import MarqueeLabel +import NextcloudKit enum Tag { static let searchField = 999 } class NCShareEmailFieldCell: UITableViewCell { + @IBOutlet weak var searchField: UITextField! - @IBOutlet weak var btnCreateLink: UIButton! - @IBOutlet weak var labelYourShare: UILabel! - @IBOutlet weak var labelShareByMail: UILabel! + @IBOutlet weak var labelOrLink: UILabel! @IBOutlet weak var btnContact: UIButton! - @IBOutlet weak var labelNoShare: UILabel! - @IBOutlet weak var heightLabelNoShare: NSLayoutConstraint! - + @IBOutlet weak var labelSeparator1: UILabel! + @IBOutlet weak var labelSeparator2: UILabel! + @IBOutlet weak var labelSendLinkByMail: UILabel! + @IBOutlet weak var labelSharedWithBy: UILabel! + @IBOutlet weak var labelResharingAllowed: UILabel! + @IBOutlet weak var topConstraintResharingView: NSLayoutConstraint! + @IBOutlet weak var viewOrLinkSeparator: UIView! + + var ocId = "" + override func awakeFromNib() { super.awakeFromNib() - // Initialization code - setupCell() } - + override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) - - // Configure the view for the selected state } - - func setupCell(){ - self.btnCreateLink.setTitle(NSLocalizedString("_create_link_", comment: ""), for: .normal) - self.btnCreateLink.layer.cornerRadius = 7 - self.btnCreateLink.layer.masksToBounds = true - self.btnCreateLink.layer.borderWidth = 1 - self.btnCreateLink.titleLabel?.font = UIFont.systemFont(ofSize: 17) - self.btnCreateLink.titleLabel!.adjustsFontSizeToFitWidth = true - self.btnCreateLink.titleLabel!.minimumScaleFactor = 0.5 - self.btnCreateLink.layer.borderColor = NCBrandColor.shared.label.cgColor - self.btnCreateLink.setTitleColor(NCBrandColor.shared.label, for: .normal) - self.btnCreateLink.backgroundColor = .clear - - self.labelShareByMail.text = NSLocalizedString("personal_share_by_mail", comment: "") - self.labelShareByMail.textColor = NCBrandColor.shared.shareByEmailTextColor - - labelYourShare.text = NSLocalizedString("_your_shares_", comment: "") + + func setupCell(with metadata: tableMetadata) { + contentView.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + ocId = metadata.ocId + + configureSearchField() + configureContactButton() + configureLabels() + updateCanReshareUI() + setNeedsLayout() + layoutIfNeeded() + } + + private func configureSearchField() { searchField.layer.cornerRadius = 5 searchField.layer.masksToBounds = true searchField.layer.borderWidth = 1 - self.searchField.text = "" - searchField.attributedPlaceholder = NSAttributedString(string: NSLocalizedString("_shareLinksearch_placeholder_", comment: ""), - attributes: [NSAttributedString.Key.foregroundColor: NCBrandColor.shared.gray60]) - searchField.textColor = NCBrandColor.shared.label searchField.layer.borderColor = NCBrandColor.shared.label.cgColor + searchField.text = "" + searchField.textColor = NCBrandColor.shared.label + searchField.attributedPlaceholder = NSAttributedString( + string: NSLocalizedString("_shareLinksearch_placeholder_", comment: ""), + attributes: [.foregroundColor: NCBrandColor.shared.gray60] + ) searchField.tag = Tag.searchField setDoneButton(sender: searchField) - + } + + private func configureContactButton() { btnContact.layer.cornerRadius = 5 btnContact.layer.masksToBounds = true btnContact.layer.borderWidth = 1 btnContact.layer.borderColor = NCBrandColor.shared.label.cgColor btnContact.tintColor = NCBrandColor.shared.label - btnContact.setImage(NCUtility().loadImage(named: "contact").image(color: NCBrandColor.shared.label, size: 24), for: .normal) - labelNoShare.textColor = NCBrandColor.shared.textInfo - labelNoShare.numberOfLines = 0 - labelNoShare.font = UIFont.systemFont(ofSize: 17) - labelNoShare.text = NSLocalizedString("no_shares_created", comment: "") + let contactImage = NCUtility().loadImage(named: "contact").image(color: NCBrandColor.shared.label, size: 24) + btnContact.setImage(contactImage, for: .normal) } - + + private func configureLabels() { + labelOrLink.text = NSLocalizedString("_share_or_", comment: "") + labelSendLinkByMail.text = NSLocalizedString("_share_send_link_by_mail_", comment: "") + labelSharedWithBy.text = NSLocalizedString("_share_received_shares_text_", comment: "") + labelResharingAllowed.text = NSLocalizedString("_share_reshare_allowed_", comment: "") + + labelSendLinkByMail.textColor = NCBrandColor.shared.label + labelSharedWithBy.textColor = NCBrandColor.shared.label + labelResharingAllowed.textColor = NCBrandColor.shared.label + } + + func updateCanReshareUI() { + guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { return } + + let isCurrentUser = NCShareCommon().isCurrentUserIsFileOwner(fileOwnerId: metadata.ownerId) + let canReshare = (metadata.sharePermissionsCollaborationServices & NCPermissions().permissionShareShare) != 0 + + labelSharedWithBy.isHidden = isCurrentUser + labelResharingAllowed.isHidden = isCurrentUser + + if !canReshare { + searchField.isUserInteractionEnabled = false + searchField.alpha = 0.5 + btnContact.isEnabled = false + btnContact.alpha = 0.5 + } + + if !isCurrentUser { + let ownerName = metadata.ownerDisplayName + let fullText = NSLocalizedString("_share_received_shares_text_", comment: "") + " " + ownerName + let attributed = NSMutableAttributedString(string: fullText) + + if let range = fullText.range(of: ownerName) { + let nsRange = NSRange(range, in: fullText) + attributed.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: 16), range: nsRange) + } + + labelSharedWithBy.attributedText = attributed + labelSharedWithBy.numberOfLines = 0 + + labelResharingAllowed.text = canReshare + ? NSLocalizedString("_share_reshare_allowed_", comment: "") + : NSLocalizedString("_share_reshare_not_allowed_", comment: "") + + topConstraintResharingView.constant = 15 + } else { + topConstraintResharingView.constant = 0 + } + + viewOrLinkSeparator.isHidden = !canReshare + } + + func updateShareUI(ocId: String, count: Int) { + guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { return } + + let isCurrentUser = NCShareCommon().isCurrentUserIsFileOwner(fileOwnerId: metadata.ownerId) + let canReshare = (metadata.sharePermissionsCollaborationServices & NCPermissions().permissionShareShare) != 0 + + if !isCurrentUser { + if canReshare { + labelOrLink.isHidden = true + labelSeparator1.isHidden = true + labelSeparator2.isHidden = true + } + } + } + @objc func cancelDatePicker() { self.searchField.endEditing(true) } - - func setDoneButton(sender: UITextField) { - //ToolBar - let toolbar = UIToolbar(); + + private func setDoneButton(sender: UITextField) { + let toolbar = UIToolbar() toolbar.sizeToFit() - let doneButton = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: .plain, target: self, action: #selector(cancelDatePicker)); - let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil) - toolbar.setItems([spaceButton, doneButton], animated: false) + let doneButton = UIBarButtonItem( + title: NSLocalizedString("_done_", comment: ""), + style: .plain, + target: self, + action: #selector(cancelDatePicker) + ) + let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + toolbar.setItems([space, doneButton], animated: false) sender.inputAccessoryView = toolbar } + + @IBAction func touchUpInsideFavorite(_ sender: UIButton) { + // Hook for favorite action if needed + } + + @IBAction func touchUpInsideDetails(_ sender: UIButton) { + // Hook for toggling detail visibility if needed + } + + @objc func longTap(_ sender: UIGestureRecognizer) { + let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_copied_path_") + NCContentPresenter().showInfo(error: error) + } } diff --git a/iOSClient/Share/NCShareEmailFieldCell.xib b/iOSClient/Share/NCShareEmailFieldCell.xib index 72cbdfe2d3..cfeb42cc45 100644 --- a/iOSClient/Share/NCShareEmailFieldCell.xib +++ b/iOSClient/Share/NCShareEmailFieldCell.xib @@ -1,9 +1,9 @@ - + - + @@ -11,112 +11,161 @@ - - + + - - + + - - + + - - + - - - - + - + - - - - - + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - + + + + - + - - - - - - - + + + + + + + + + + - + diff --git a/iOSClient/Share/NCShareEmailLinkHeaderView.swift b/iOSClient/Share/NCShareEmailLinkHeaderView.swift new file mode 100644 index 0000000000..9502164891 --- /dev/null +++ b/iOSClient/Share/NCShareEmailLinkHeaderView.swift @@ -0,0 +1,43 @@ +// +// NCShareEmailLinkHeaderView.swift +// Nextcloud +// +// Created by A106551118 on 12/08/25. +// Copyright © 2025 Marino Faggiana. All rights reserved. +// + +import UIKit + +class NCShareEmailLinkHeaderView: UITableViewHeaderFooterView { + private let label = UILabel() + static let reuseIdentifier = "NCShareEmailLinkHeaderView" + + override init(reuseIdentifier: String?) { + super.init(reuseIdentifier: reuseIdentifier) + setup() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setup() + } + + private func setup() { + contentView.backgroundColor = .clear + label.translatesAutoresizingMaskIntoConstraints = false + label.font = .systemFont(ofSize: 17, weight: .semibold) + label.textColor = NCBrandColor.shared.textColor + contentView.addSubview(label) + + NSLayoutConstraint.activate([ + label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20), + label.heightAnchor.constraint(equalToConstant: 30), + label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15), + label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15), + ]) + } + + func configure(text: String) { + label.text = text + } +} diff --git a/iOSClient/Share/NCShareHeader.swift b/iOSClient/Share/NCShareHeader.swift index 862c8f7008..c484c88592 100644 --- a/iOSClient/Share/NCShareHeader.swift +++ b/iOSClient/Share/NCShareHeader.swift @@ -23,12 +23,15 @@ import UIKit -class NCShareAdvancePermissionHeader: UIView { +class NCShareAdvancePermissionHeader: UITableViewHeaderFooterView { @IBOutlet weak var imageView: UIImageView! @IBOutlet weak var fileName: UILabel! @IBOutlet weak var info: UILabel! @IBOutlet weak var favorite: UIButton! @IBOutlet weak var fullWidthImageView: UIImageView! + + static let reuseIdentifier = "NCShareAdvancePermissionHeader" + var ocId = "" let utility = NCUtility() let utilityFileSystem = NCUtilityFileSystem() @@ -37,31 +40,84 @@ class NCShareAdvancePermissionHeader: UIView { backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground fileName.textColor = NCBrandColor.shared.label info.textColor = NCBrandColor.shared.textInfo - backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + + let isShare = metadata.permissions.contains(NCPermissions().permissionShared) + if let image = NCUtility().getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt1024) { fullWidthImageView.image = image fullWidthImageView.contentMode = .scaleAspectFill imageView.isHidden = true } else { - if metadata.directory { - imageView.image = metadata.e2eEncrypted ? NCImageCache.shared.getFolderEncrypted() : NCImageCache.shared.getFolder() + imageView.isHidden = false + if metadata.e2eEncrypted { + imageView.image = NCImageCache.shared.getFolderEncrypted() + } else if isShare { + imageView.image = NCImageCache.shared.getFolderSharedWithMe() + } else if !metadata.shareType.isEmpty { + imageView.image = metadata.shareType.contains(3) + ? NCImageCache.shared.getFolderPublic() + : NCImageCache.shared.getFolderSharedWithMe() + } else if metadata.directory { + imageView.image = NCImageCache.shared.getFolder() } else if !metadata.iconName.isEmpty { imageView.image = NCUtility().loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account) } else { imageView.image = NCImageCache.shared.getImageFile() } } - favorite.setNeedsUpdateConstraints() - favorite.layoutIfNeeded() + fileName.text = metadata.fileNameView fileName.textColor = NCBrandColor.shared.fileFolderName - if metadata.favorite { - favorite.setImage(utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.yellowFavorite], size: 24), for: .normal) + + updateFavoriteIcon(isFavorite: metadata.favorite) + info.text = utilityFileSystem.transformedSize(metadata.size) + ", " + utility.getRelativeDateTitle(metadata.date as Date) + } + + func setupUI(with metadata: tableMetadata, linkCount: Int, emailCount: Int) { + contentView.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground + fileName.textColor = NCBrandColor.shared.label + info.textColor = NCBrandColor.shared.textInfo + +// let isShare = metadata.permissions.contains(NCPermissions().permissionShared) + let hasShares = (linkCount > 0 || emailCount > 0) + + if let image = NCUtility().getImage(ocId: metadata.ocId, + etag: metadata.etag, + ext: NCGlobal.shared.previewExt1024) { + fullWidthImageView.image = image + fullWidthImageView.contentMode = .scaleAspectFill + imageView.isHidden = true } else { - favorite.setImage(utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.textInfo], size: 24), for: .normal) + imageView.isHidden = false + if metadata.e2eEncrypted { + imageView.image = NCImageCache.shared.getFolderEncrypted() + } else if hasShares { + imageView.image = NCImageCache.shared.getFolderSharedWithMe() + } else if !metadata.shareType.isEmpty { + imageView.image = metadata.shareType.contains(3) + ? NCImageCache.shared.getFolderPublic() + : NCImageCache.shared.getFolderSharedWithMe() + } else if metadata.directory { + imageView.image = NCImageCache.shared.getFolder() + } else if !metadata.iconName.isEmpty { + imageView.image = NCUtility().loadImage(named: metadata.iconName, + useTypeIconFile: true, + account: metadata.account) + } else { + imageView.image = NCImageCache.shared.getImageFile() + } } - info.textColor = NCBrandColor.shared.optionItem - info.text = utilityFileSystem.transformedSize(metadata.size) + ", " + utility.dateDiff(metadata.date as Date) + + fileName.text = metadata.fileNameView + fileName.textColor = NCBrandColor.shared.fileFolderName + + updateFavoriteIcon(isFavorite: metadata.favorite) + info.text = utilityFileSystem.transformedSize(metadata.size) + ", " + utility.getRelativeDateTitle(metadata.date as Date) + } + + private func updateFavoriteIcon(isFavorite: Bool) { + let color = isFavorite ? NCBrandColor.shared.yellowFavorite : NCBrandColor.shared.textInfo + favorite.setImage(utility.loadImage(named: "star.fill", colors: [color], size: 24), for: .normal) } @IBAction func touchUpInsideFavorite(_ sender: UIButton) { @@ -69,11 +125,7 @@ class NCShareAdvancePermissionHeader: UIView { NCNetworking.shared.favoriteMetadata(metadata) { error in if error == .success { guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) else { return } - if metadata.favorite { - self.favorite.setImage(self.utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.yellowFavorite], size: 24), for: .normal) - } else { - self.favorite.setImage(self.utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.textInfo], size: 24), for: .normal) - } + self.updateFavoriteIcon(isFavorite: metadata.favorite) } else { NCContentPresenter().showError(error: error) } diff --git a/iOSClient/Share/NCShareHeader.xib b/iOSClient/Share/NCShareHeader.xib index 55ffce6cab..69ae9e03bd 100644 --- a/iOSClient/Share/NCShareHeader.xib +++ b/iOSClient/Share/NCShareHeader.xib @@ -1,131 +1,128 @@ - - + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + - - + + - - - - - - - - - - + + + diff --git a/iOSClient/Share/NCShareLinkCell.swift b/iOSClient/Share/NCShareLinkCell.swift index 631b989d66..d70df0a755 100644 --- a/iOSClient/Share/NCShareLinkCell.swift +++ b/iOSClient/Share/NCShareLinkCell.swift @@ -23,32 +23,27 @@ 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 labelTitle: UILabel! + @IBOutlet weak var buttonDetail: UIButton! + @IBOutlet weak var buttonCopy: UIButton! + @IBOutlet weak var btnQuickStatus: UIButton! + @IBOutlet weak var imagePermissionType: UIImageView! + @IBOutlet weak var imageExpiredDateSet: UIImageView! + @IBOutlet weak var imagePasswordSet: UIImageView! + @IBOutlet weak var imageAllowedPermission: UIImageView! + @IBOutlet weak var imageRightArrow: UIImageView! @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! - -// @IBOutlet weak var imageItem: UIImageView! -// @IBOutlet weak var labelTitle: UILabel! -// @IBOutlet weak var buttonCopy: UIButton! -// @IBOutlet weak var buttonMenu: UIButton! -// @IBOutlet weak var status: UILabel! -// @IBOutlet weak var btnQuickStatus: UIButton! -// @IBOutlet weak var imageDownArrow: UIImageView! -// @IBOutlet weak var labelQuickStatus: UILabel! - - private let iconShare: CGFloat = 200 - var tableShare: tableShare? - var isDirectory = false + @IBOutlet weak var leadingContraintofImageRightArrow: NSLayoutConstraint! + + private let iconShareSize: CGFloat = 200 + weak var delegate: NCShareLinkCellDelegate? + + var tableShare: tableShare? var isInternalLink = false + var isDirectory = false var indexPath = IndexPath() - let utility = NCUtility() override func prepareForReuse() { super.prepareForReuse() @@ -56,114 +51,86 @@ class NCShareLinkCell: UITableViewCell { tableShare = nil } - 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 + override func awakeFromNib() { + super.awakeFromNib() + setupCellAppearance() + } - copyButton.setImage(UIImage(systemName: "doc.on.doc")?.withTintColor(.label, renderingMode: .alwaysOriginal), for: .normal) - copyButton.accessibilityLabel = NSLocalizedString("_copy_", comment: "") - - menuButton.accessibilityLabel = NSLocalizedString("_more_", comment: "") - menuButton.accessibilityIdentifier = "showShareLinkDetails" - - if isInternalLink { - labelTitle.text = NSLocalizedString("_share_internal_link_", comment: "") - descriptionLabel.text = NSLocalizedString("_share_internal_link_des_", comment: "") - 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))" - } - } else { - menuImageName = "plus" - menuButton.accessibilityLabel = NSLocalizedString("_add_", comment: "") - menuButton.accessibilityIdentifier = "addShareLink" - } - - imageItem.image = NCUtility().loadImage(named: "link.circle.fill", colors: [NCBrandColor.shared.getElement(account: tableShare?.account)]) - menuButton.setImage(NCUtility().loadImage(named: menuImageName, colors: [NCBrandColor.shared.iconImageColor]), for: .normal) - } - - 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 NCSharePermissions.getPermissionValue(canRead: false, canCreate: true, canEdit: false, canDelete: false, canShare: false, isDirectory: isDirectory) == tableShare.permissions { // File request - labelQuickStatus.text = NSLocalizedString("_share_file_drop_", comment: "") - } - if NCSharePermissions.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)]) - } + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + if previousTraitCollection?.userInterfaceStyle != traitCollection.userInterfaceStyle { + setupCellAppearance() } - - 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]) } - override func awakeFromNib() { - super.awakeFromNib() - buttonMenu.contentMode = .scaleAspectFill - imageItem.image = UIImage(named: "sharebylink")?.image(color: NCBrandColor.shared.label, size: 30) - buttonCopy.setImage(UIImage.init(named: "shareCopy")!.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) - buttonMenu.setImage(UIImage.init(named: "shareMenu")!.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) - labelQuickStatus.textColor = NCBrandColor.shared.customer + func configure(with share: tableShare?, at indexPath: IndexPath, isDirectory: Bool, title: String) { + self.tableShare = share + self.indexPath = indexPath + self.isDirectory = isDirectory + setupCellAppearance(titleAppendString: title) + +// let shareLinksCountString = shareLinksCount > 0 ? String(shareLinksCount) : "" +// setupCellAppearance(titleAppendString: shareLinksCountString) +// setupCellAppearance(titleAppendString: String(shareLinksCount)) } - func setupCellUI() { - let permissions = NCPermissions() - guard let tableShare = tableShare else { - return - } + private func setupCellAppearance(titleAppendString: String? = nil) { contentView.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground labelTitle.textColor = NCBrandColor.shared.label - - if tableShare.permissions == permissions.permissionCreateShare { - labelQuickStatus.text = NSLocalizedString("_share_file_drop_", comment: "") - } else { - // Read Only - if permissions.isAnyPermissionToEdit(tableShare.permissions) { - labelQuickStatus.text = NSLocalizedString("_share_editing_", comment: "") + labelQuickStatus.textColor = NCBrandColor.shared.shareBlueColor + + buttonDetail.setTitleColor(NCBrandColor.shared.shareBlackColor, for: .normal) + buttonCopy.setImage(UIImage(named: "share")?.image(color: NCBrandColor.shared.brand, size: 24), for: .normal) + + imageRightArrow.image = UIImage(named: "rightArrow")?.imageColor(NCBrandColor.shared.shareBlueColor) + imageExpiredDateSet.image = UIImage(named: "calenderNew")?.imageColor(NCBrandColor.shared.shareBlueColor) + imagePasswordSet.image = UIImage(named: "lockNew")?.imageColor(NCBrandColor.shared.shareBlueColor) + + buttonDetail.setTitle(NSLocalizedString("_share_details_", comment: ""), for: .normal) + labelTitle.text = NSLocalizedString("_share_link_", comment: "") + + if let tableShare = tableShare, let titleAppendString { + if !tableShare.label.isEmpty { + labelTitle.text? += " (\(tableShare.label))" } else { - labelQuickStatus.text = NSLocalizedString("_share_read_only_", comment: "") + labelTitle.text?.append(" \(titleAppendString)") } } + updatePermissionUI() } - + + private func updatePermissionUI() { + guard let tableShare = tableShare else { return } + + let permissions = NCPermissions() + + if tableShare.permissions == permissions.permissionCreateShare { + labelQuickStatus.text = NSLocalizedString("_share_quick_permission_everyone_can_just_upload_", comment: "") + imagePermissionType.image = UIImage(named: "upload")?.imageColor(NCBrandColor.shared.shareBlueColor) + } else if permissions.isAnyPermissionToEdit(tableShare.permissions) { + labelQuickStatus.text = NSLocalizedString("_share_quick_permission_everyone_can_edit_", comment: "") + imagePermissionType.image = UIImage(named: "editNew")?.imageColor(NCBrandColor.shared.shareBlueColor) + } else { + labelQuickStatus.text = NSLocalizedString("_share_quick_permission_everyone_can_only_view_", comment: "") + imagePermissionType.image = UIImage(named: "showPasswordNew")?.imageColor(NCBrandColor.shared.shareBlueColor) + } + + imagePasswordSet.isHidden = tableShare.password.isEmpty + imageExpiredDateSet.isHidden = (tableShare.expirationDate == nil) + + leadingContraintofImageRightArrow.constant = (imagePasswordSet.isHidden && imageExpiredDateSet.isHidden) ? 0 : 5 + } + + // MARK: - Actions + @IBAction func touchUpInsideCopy(_ sender: Any) { delegate?.tapCopy(with: tableShare, sender: sender) } - - @IBAction func touchUpInsideMenu(_ sender: Any) { + + @IBAction func touchUpInsideDetail(_ sender: Any) { delegate?.tapMenu(with: tableShare, sender: sender) } - @objc func openQuickStatus(_ sender: UITapGestureRecognizer) { - } - @IBAction func quickStatusClicked(_ sender: UIButton) { delegate?.quickStatus(with: tableShare, sender: sender) } diff --git a/iOSClient/Share/NCShareLinkCell.xib b/iOSClient/Share/NCShareLinkCell.xib index d5810a5294..9783995c5e 100755 --- a/iOSClient/Share/NCShareLinkCell.xib +++ b/iOSClient/Share/NCShareLinkCell.xib @@ -1,149 +1,152 @@ - + - - - + - - + + - - + + - - + + + + + + - - - - + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + - + - - - - - - - - - - - - - + + + + diff --git a/iOSClient/Share/NCShareNavigationTitleSetting.swift b/iOSClient/Share/NCShareNavigationTitleSetting.swift index 9804dea108..b2c8866ee1 100644 --- a/iOSClient/Share/NCShareNavigationTitleSetting.swift +++ b/iOSClient/Share/NCShareNavigationTitleSetting.swift @@ -18,7 +18,7 @@ extension NCShareNavigationTitleSetting where Self: UIViewController { func setNavigationTitle() { title = NSLocalizedString("_share_", comment: "") + " – " - if share.shareType == NCShareCommon.shareTypeLink { + if share.shareType == NCShareCommon().SHARE_TYPE_LINK { title! += share.label.isEmpty ? NSLocalizedString("_share_link_", comment: "") : share.label } else { title! += share.shareWithDisplayname.isEmpty ? share.shareWith : share.shareWithDisplayname diff --git a/iOSClient/Share/NCShareNetworking.swift b/iOSClient/Share/NCShareNetworking.swift index 5e42777a00..935c9e61e9 100644 --- a/iOSClient/Share/NCShareNetworking.swift +++ b/iOSClient/Share/NCShareNetworking.swift @@ -36,32 +36,32 @@ class NCShareNetworking: NSObject { self.view = view self.delegate = delegate self.session = session - super.init() } - private func readDownloadLimit(account: String, token: String) async throws -> NKDownloadLimit? { - return try await withCheckedThrowingContinuation { continuation in - NextcloudKit.shared.getDownloadLimit(account: account, token: token) { limit, error in - if error != .success { - continuation.resume(throwing: error.error) - return - } else { - continuation.resume(returning: limit) - } - } - } - } - - func readDownloadLimits(account: String, tokens: [String]) async throws { - for token in tokens { - self.database.deleteDownloadLimit(byAccount: account, shareToken: token) - if let downloadLimit = try await readDownloadLimit(account: account, token: token) { - self.database.createDownloadLimit(account: account, count: downloadLimit.count, limit: downloadLimit.limit, token: token) - } - } - } - +// private func readDownloadLimit(account: String, token: String) async throws -> NKDownloadLimit? { +// return try await withCheckedThrowingContinuation { continuation in +// NextcloudKit.shared.getDownloadLimit(account: account, token: token) { limit, error in +// if error != .success { +// continuation.resume(throwing: error.error) +// return +// } else { +// continuation.resume(returning: limit) +// } +// } +// } +// } +// +// func readDownloadLimits(account: String, tokens: [String]) async throws { +// for token in tokens { +// self.database.deleteDownloadLimit(byAccount: account, shareToken: token) +// if let downloadLimit = try await readDownloadLimit(account: account, token: token) { +// self.database.createDownloadLimit(account: account, count: downloadLimit.count, limit: downloadLimit.limit, token: token) +// } +// } +// } + + // MARK: - Read func readShare(showLoadingIndicator: Bool) { if showLoadingIndicator { NCActivityIndicator.shared.start(backgroundView: view) @@ -69,26 +69,13 @@ class NCShareNetworking: NSObject { let filenamePath = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, session: session) let parameter = NKShareParameter(path: filenamePath) - NextcloudKit.shared.readShares(parameters: parameter, account: metadata.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.metadata.account, - path: filenamePath, - name: "readShares") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { account, shares, _, error in + NextcloudKit.shared.readShares(parameters: parameter, account: metadata.account) { account, shares, _, error in if error == .success, let shares = shares { self.database.deleteTableShare(account: account, path: "/" + filenamePath) let home = self.utilityFileSystem.getHomeServer(session: self.session) self.database.addShare(account: self.metadata.account, home: home, shares: shares) - NextcloudKit.shared.getGroupfolders(account: account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, - name: "getGroupfolders") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { account, results, _, error in + NextcloudKit.shared.getGroupfolders(account: account) { account, results, _, error in if showLoadingIndicator { NCActivityIndicator.shared.stop() } @@ -97,11 +84,8 @@ class NCShareNetworking: NSObject { } Task { - try? await self.readDownloadLimits(account: account, tokens: shares.map(\.token)) - - Task { @MainActor in - self.delegate?.readShareCompleted() - } + try await self.readDownloadLimits(account: account, tokens: shares.map(\.token)) + self.delegate?.readShareCompleted() } } } else { @@ -113,47 +97,51 @@ class NCShareNetworking: NSObject { } } } - + + // MARK: - Create Share Link func createShareLink(password: String?) { NCActivityIndicator.shared.start(backgroundView: view) let filenamePath = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, session: session) - NextcloudKit.shared.createShareLink(path: filenamePath, account: metadata.account) { [self] account, share, data, error in + + NextcloudKit.shared.createShareLink(path: filenamePath, account: metadata.account) { [weak self] account, share, _, error in + guard let self = self else { return } NCActivityIndicator.shared.stop() - if error == .success && share != nil { + + if error == .success, let share = share { let home = self.utilityFileSystem.getHomeServer(session: self.session) - NCManageDatabase.shared.addShare(account: self.metadata.account, home:home, shares: [share!]) - if !metadata.contentType.contains("directory") { - AnalyticsHelper.shared.trackEventWithMetadata(eventName: .EVENT__SHARE_FILE ,metadata: metadata) + self.database.addShare(account: self.metadata.account, home: home, shares: [share]) + + if !self.metadata.contentType.contains("directory") { + AnalyticsHelper.shared.trackEventWithMetadata(eventName: .EVENT__SHARE_FILE, metadata: self.metadata) } - } else if error != .success{ + + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDidCreateShareLink) + // 🔄 ensure we sync DB + UI with server + self.readShare(showLoadingIndicator: false) + } else { NCContentPresenter().showError(error: error) } + self.delegate?.shareCompleted() } } + // MARK: - Create Share (user/email/group) func createShare(_ shareable: Shareable, downloadLimit: DownloadLimitViewModel) { NCActivityIndicator.shared.start(backgroundView: view) let filenamePath = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, session: session) - let capabilities = NCNetworking.shared.capabilities[self.metadata.account] ?? NKCapabilities.Capabilities() - - NextcloudKit.shared.createShare(path: filenamePath, - shareType: shareable.shareType, - shareWith: shareable.shareWith, - publicUpload: false, - note: shareable.note, - hideDownload: false, - password: shareable.password, - permissions: shareable.permissions, - attributes: shareable.attributes, - account: metadata.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.metadata.account, - path: filenamePath, - name: "createShare") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { _, share, _, error in + + NextcloudKit.shared.createShare( + path: filenamePath, + shareType: shareable.shareType, + shareWith: shareable.shareWith, + password: shareable.password, + note: shareable.note, + permissions: shareable.permissions, + attributes: shareable.attributes, + account: metadata.account + ) { [weak self] _, share, _, error in + guard let self = self else { return } NCActivityIndicator.shared.stop() if error == .success, let share = share { @@ -161,28 +149,29 @@ class NCShareNetworking: NSObject { let home = self.utilityFileSystem.getHomeServer(session: self.session) self.database.addShare(account: self.metadata.account, home: home, shares: [share]) + let directory = self.metadata.directory + if shareable.hasChanges(comparedTo: share) { self.updateShare(shareable, downloadLimit: downloadLimit) // Download limit update should happen implicitly on share update. } else { - if case let .limited(limit, _) = downloadLimit, - capabilities.fileSharingDownloadLimit, - shareable.shareType == NCShareCommon.shareTypeLink, - shareable.itemType == NCShareCommon.itemTypeFile { - self.setShareDownloadLimit(limit, token: share.token) - } - } - - Task { - await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferRequestData(serverUrl: self.metadata.serverUrl) + if share.shareType == NCShareCommon().SHARE_TYPE_LINK && !directory { + if case let .limited(limit, _) = downloadLimit, NCCapabilities.shared.getCapabilities(account: self.metadata.account).capabilityFileSharingDownloadLimit { + self.setShareDownloadLimit(limit, token: share.token) + } } } - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterUpdateShare, userInfo: ["account": self.metadata.account, "serverUrl": self.metadata.serverUrl]) if !self.metadata.contentType.contains("directory") { - AnalyticsHelper.shared.trackEventWithMetadata(eventName: .EVENT__SHARE_FILE ,metadata: self.metadata) + AnalyticsHelper.shared.trackEventWithMetadata(eventName: .EVENT__SHARE_FILE, metadata: self.metadata) } + + NotificationCenter.default.postOnMainThread( + name: NCGlobal.shared.notificationCenterUpdateShare, + userInfo: ["account": self.metadata.account, "serverUrl": self.metadata.serverUrl] + ) + // 🔄 ensure consistency + self.readShare(showLoadingIndicator: false) } else { NCContentPresenter().showError(error: error) } @@ -191,67 +180,75 @@ class NCShareNetworking: NSObject { } } + // MARK: - Unshare func unShare(idShare: Int) { NCActivityIndicator.shared.start(backgroundView: view) - NextcloudKit.shared.deleteShare(idShare: idShare, account: metadata.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.metadata.account, - path: "_\(idShare)", - name: "deleteShare") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { account, _, error in + NextcloudKit.shared.deleteShare(idShare: idShare, account: metadata.account) { [weak self] account, _, error in + guard let self = self else { return } NCActivityIndicator.shared.stop() if error == .success { self.database.deleteTableShare(account: account, idShare: idShare) self.delegate?.unShareCompleted() - Task { - await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferRequestData(serverUrl: self.metadata.serverUrl) - } - } + NotificationCenter.default.postOnMainThread( + name: NCGlobal.shared.notificationCenterUpdateShare, + userInfo: ["account": self.metadata.account, "serverUrl": self.metadata.serverUrl] + ) + // 🔄 reload to avoid missing/duplicate shares + self.readShare(showLoadingIndicator: false) } else { NCContentPresenter().showError(error: error) } } } + // MARK: - Update Share func updateShare(_ shareable: Shareable, downloadLimit: DownloadLimitViewModel) { NCActivityIndicator.shared.start(backgroundView: view) - NextcloudKit.shared.updateShare(idShare: shareable.idShare, password: shareable.password, expireDate: shareable.formattedDateString, permissions: shareable.permissions, note: shareable.note, label: shareable.label, hideDownload: shareable.hideDownload, attributes: shareable.attributes, account: metadata.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.metadata.account, - path: "_\(shareable.idShare)", - name: "updateShare") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { _, share, _, error in + NextcloudKit.shared.updateShare( + idShare: shareable.idShare, + password: shareable.password, + expireDate: shareable.formattedDateString, + permissions: shareable.permissions, + note: shareable.note, + label: shareable.label, + hideDownload: shareable.hideDownload, + attributes: shareable.attributes, + account: metadata.account + ) { [weak self] _, share, _, error in + guard let self = self else { return } NCActivityIndicator.shared.stop() if error == .success, let share = share { let home = self.utilityFileSystem.getHomeServer(session: self.session) - let capabilities = NCNetworking.shared.capabilities[self.metadata.account] ?? NKCapabilities.Capabilities() - self.database.addShare(account: self.metadata.account, home: home, shares: [share]) self.delegate?.readShareCompleted() - if capabilities.fileSharingDownloadLimit, - shareable.shareType == NCShareCommon.shareTypeLink, - shareable.itemType == NCShareCommon.itemTypeFile { - if case let .limited(limit, _) = downloadLimit { - self.setShareDownloadLimit(limit, token: share.token) - } else { - self.removeShareDownloadLimit(token: share.token) - } - } - - Task { - await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferRequestData(serverUrl: self.metadata.serverUrl) + let directory = self.metadata.directory + +// if capabilities.fileSharingDownloadLimit, +// shareable.shareType == NCShareCommon.shareTypeLink, +// shareable.itemType == NCShareCommon.itemTypeFile { + if share.shareType == NCShareCommon().SHARE_TYPE_LINK && !directory{ + if NCCapabilities.shared.getCapabilities(account: self.metadata.account).capabilityFileSharingDownloadLimit { + if case let .limited(limit, _) = downloadLimit { + self.setShareDownloadLimit(limit, token: share.token) + } else { + self.removeShareDownloadLimit(token: share.token) + } } } + + NotificationCenter.default.postOnMainThread( + name: NCGlobal.shared.notificationCenterUpdateShare, + userInfo: ["account": self.metadata.account, "serverUrl": self.metadata.serverUrl] + ) + NotificationCenter.default.postOnMainThread( + name: NCGlobal.shared.notificationCenterReloadDataNCShare + ) + // 🔄 refresh again from server + self.readShare(showLoadingIndicator: false) } else { NCContentPresenter().showError(error: error) self.delegate?.updateShareWithError(idShare: shareable.idShare) @@ -261,14 +258,7 @@ class NCShareNetworking: NSObject { func getSharees(searchString: String) { NCActivityIndicator.shared.start(backgroundView: view) - NextcloudKit.shared.searchSharees(search: searchString, account: metadata.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.metadata.account, - path: searchString, - name: "searchSharees") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { _, sharees, _, error in + NextcloudKit.shared.searchSharees(search: searchString, account: metadata.account) { _, sharees, _, error in NCActivityIndicator.shared.stop() if error == .success { @@ -285,18 +275,35 @@ class NCShareNetworking: NSObject { /// /// Remove the download limit on the share, if existent. /// - func removeShareDownloadLimit(token: String) { - let capabilities = NCNetworking.shared.capabilities[self.metadata.account] ?? NKCapabilities.Capabilities() + private func readDownloadLimit(account: String, token: String) async throws -> NKDownloadLimit? { + return try await withCheckedThrowingContinuation { continuation in + NextcloudKit.shared.getDownloadLimit(account: account, token: token) { limit, error in + if error != .success { + continuation.resume(throwing: error.error) + return + } else { + continuation.resume(returning: limit) + } + } + } + } - if !capabilities.fileSharingDownloadLimit || token.isEmpty { - return + func readDownloadLimits(account: String, tokens: [String]) async throws { + for token in tokens { + self.database.deleteDownloadLimit(byAccount: account, shareToken: token) + if let downloadLimit = try await readDownloadLimit(account: account, token: token) { + self.database.createDownloadLimit(account: account, count: downloadLimit.count, limit: downloadLimit.limit, token: token) + } } + } + func removeShareDownloadLimit(token: String) { + if !NCCapabilities.shared.getCapabilities(account: self.metadata.account).capabilityFileSharingDownloadLimit || token.isEmpty { + return + } NCActivityIndicator.shared.start(backgroundView: view) - NextcloudKit.shared.removeShareDownloadLimit(account: metadata.account, token: token) { error in NCActivityIndicator.shared.stop() - if error == .success { self.delegate?.downloadLimitRemoved(by: token) } else { @@ -305,23 +312,13 @@ class NCShareNetworking: NSObject { } } - /// - /// Set the download limit for the share. - /// - /// - Parameter limit: The new download limit to set. - /// func setShareDownloadLimit(_ limit: Int, token: String) { - let capabilities = NCNetworking.shared.capabilities[self.metadata.account] ?? NKCapabilities.Capabilities() - - if !capabilities.fileSharingDownloadLimit || token.isEmpty { + if !NCCapabilities.shared.getCapabilities(account: self.metadata.account).capabilityFileSharingDownloadLimit || token.isEmpty { return } - NCActivityIndicator.shared.start(backgroundView: view) - NextcloudKit.shared.setShareDownloadLimit(account: metadata.account, token: token, limit: limit) { error in NCActivityIndicator.shared.stop() - if error == .success { self.delegate?.downloadLimitSet(to: limit, by: token) } else { diff --git a/iOSClient/Share/NCSharePaging.swift b/iOSClient/Share/NCSharePaging.swift index 9645b2f992..c1e9fc0a2b 100644 --- a/iOSClient/Share/NCSharePaging.swift +++ b/iOSClient/Share/NCSharePaging.swift @@ -50,9 +50,7 @@ class NCSharePaging: UIViewController { view.backgroundColor = .systemBackground title = NSLocalizedString("_details_", comment: "") - navigationController?.navigationBar.tintColor = NCBrandColor.shared.iconImageColor - navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_close_", comment: ""), style: .done, target: self, action: #selector(exitTapped(_:))) - + navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: .done, target: self, action: #selector(exitTapped)) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground(notification:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidEnterBackground), object: nil) @@ -107,12 +105,7 @@ class NCSharePaging: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - - navigationController?.setNavigationBarAppearance() - - let capabilities = NCNetworking.shared.capabilities[metadata.account] ?? NKCapabilities.Capabilities() - - if !capabilities.fileSharingApiEnabled && !capabilities.filesComments && capabilities.activity.isEmpty { + if NCCapabilities.shared.disableSharesView(account: metadata.account) { self.dismiss(animated: false, completion: nil) } @@ -123,11 +116,7 @@ class NCSharePaging: UIViewController { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - Task { - await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: metadata.serverUrl, status: nil) - } - } + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource, userInfo: ["serverUrl": metadata.serverUrl]) } deinit { @@ -167,7 +156,7 @@ class NCSharePaging: UIViewController { view.frame.origin.y = 0 } - @objc func exitTapped(_ sender: Any?) { + @objc func exitTapped() { self.dismiss(animated: true, completion: nil) } @@ -179,6 +168,7 @@ class NCSharePaging: UIViewController { // MARK: - PagingViewController Delegate extension NCSharePaging: PagingViewControllerDelegate { + func pagingViewController(_ pagingViewController: PagingViewController, willScrollToItem pagingItem: PagingItem, startingViewController: UIViewController, destinationViewController: UIViewController) { currentVC?.textField?.resignFirstResponder() @@ -189,6 +179,7 @@ extension NCSharePaging: PagingViewControllerDelegate { // MARK: - PagingViewController DataSource extension NCSharePaging: PagingViewControllerDataSource { + func pagingViewController(_: PagingViewController, viewControllerAt index: Int) -> UIViewController { let height = pagingViewController.options.menuHeight + NCSharePagingView.headerHeight + NCSharePagingView.tagHeaderHeight @@ -235,9 +226,14 @@ 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, @@ -266,6 +262,7 @@ class NCSharePagingView: PagingView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + } class NCShareHeaderView: UIView { @@ -287,8 +284,6 @@ class NCShareHeaderView: UIView { override func awakeFromNib() { super.awakeFromNib() - let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(longTap(_:))) - path.addGestureRecognizer(longGesture) setupUI() } @@ -339,15 +334,4 @@ class NCShareHeaderView: UIView { } } } - - @IBAction func touchUpInsideDetails(_ sender: UIButton) { - creation.isHidden = !creation.isHidden - upload.isHidden = !upload.isHidden - } - - @objc func longTap(_ sender: UIGestureRecognizer) { - UIPasteboard.general.string = path.text - let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_copied_path_") - NCContentPresenter().showInfo(error: error) - } } diff --git a/iOSClient/Share/NCShareUserCell.swift b/iOSClient/Share/NCShareUserCell.swift index 227a73f217..a50a15b0a0 100644 --- a/iOSClient/Share/NCShareUserCell.swift +++ b/iOSClient/Share/NCShareUserCell.swift @@ -26,151 +26,124 @@ import NextcloudKit class NCShareUserCell: UITableViewCell, NCCellProtocol { - @IBOutlet weak var imageItem: UIImageView! + // MARK: - IBOutlets @IBOutlet weak var labelTitle: UILabel! @IBOutlet weak var buttonMenu: UIButton! - @IBOutlet weak var imageStatus: UIImageView! - @IBOutlet weak var status: UILabel! @IBOutlet weak var btnQuickStatus: UIButton! @IBOutlet weak var labelQuickStatus: UILabel! - @IBOutlet weak var imageDownArrow: UIImageView! - @IBOutlet weak var labelCanEdit: UILabel! - @IBOutlet weak var switchCanEdit: UISwitch! - private var index = IndexPath() - + @IBOutlet weak var imagePermissionType: UIImageView! + @IBOutlet weak var imageRightArrow: UIImageView! + @IBOutlet weak var imageExpiredDateSet: UIImageView! + @IBOutlet weak var imagePasswordSet: UIImageView! + @IBOutlet weak var imageAllowedPermission: UIImageView! + @IBOutlet weak var leadingContraintofImageRightArrow: NSLayoutConstraint! + + // MARK: - Properties + private var indexPathInternal = IndexPath() var tableShare: tableShare? - var isDirectory = false - let utility = NCUtility() + var isDirectory: Bool = false weak var delegate: NCShareUserCellDelegate? var indexPath: IndexPath { - get { return index } - set { index = newValue } + get { indexPathInternal } + set { indexPathInternal = newValue } } + var fileUser: String? { get { return tableShare?.shareWith } set {} } - func setupCellUI(userId: String, session: NCSession.Session, metadata: tableMetadata) { - 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) - if !type.isEmpty { - labelTitle.text?.append(" (\(type))") + // MARK: - Lifecycle + override func awakeFromNib() { + super.awakeFromNib() + setupCellUIAppearance() + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + if previousTraitCollection?.userInterfaceStyle != traitCollection.userInterfaceStyle { + setupCellUIAppearance() } + } + + // MARK: - Configure + func configure(with share: tableShare?, at indexPath: IndexPath, isDirectory: Bool, userId: String) { + self.indexPath = indexPath + self.tableShare = share + self.isDirectory = isDirectory + setupCellUI(userId: userId) + } - labelTitle.lineBreakMode = .byTruncatingMiddle - labelTitle.textColor = NCBrandColor.shared.textColor + // MARK: - UI Setup + private func setupCellUIAppearance() { contentView.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground - let permissions = NCPermissions() - labelTitle.text = tableShare.shareWithDisplayname + buttonMenu.contentMode = .scaleAspectFill + buttonMenu.setImage(NCImageCache.images.buttonMore.image(color: NCBrandColor.shared.brand, size: 24), for: .normal) + labelQuickStatus.textColor = NCBrandColor.shared.shareBlueColor labelTitle.textColor = NCBrandColor.shared.label - isUserInteractionEnabled = true - switchCanEdit.isHidden = true - labelCanEdit.isHidden = true - buttonMenu.isHidden = false - buttonMenu.accessibilityLabel = NSLocalizedString("_more_", comment: "") - imageItem.image = NCShareCommon.getImageShareType(shareType: tableShare.shareType) - - let status = utility.getUserStatus(userIcon: tableShare.userIcon, userStatus: tableShare.userStatus, userMessage: tableShare.userMessage) - imageStatus.image = status.statusImage - self.status.text = status.statusMessage - - if permissions.isAnyPermissionToEdit(tableShare.permissions) { - switchCanEdit.setOn(true, animated: false) - } else { - switchCanEdit.setOn(false, animated: false) - } - - // If the initiator or the recipient is not the current user, show the list of sharees without any options to edit it. - if tableShare.uidOwner != userId && tableShare.uidFileOwner != userId { - isUserInteractionEnabled = false - switchCanEdit.isHidden = true - labelCanEdit.isHidden = true - buttonMenu.isHidden = true - } + imageRightArrow.image = UIImage(named: "rightArrow")?.imageColor(NCBrandColor.shared.shareBlueColor) + imageExpiredDateSet.image = UIImage(named: "calenderNew")?.imageColor(NCBrandColor.shared.shareBlueColor) + imagePasswordSet.image = UIImage(named: "lockNew")?.imageColor(NCBrandColor.shared.shareBlueColor) - btnQuickStatus.accessibilityHint = NSLocalizedString("_user_sharee_footer_", comment: "") - btnQuickStatus.setTitle("", for: .normal) - btnQuickStatus.contentHorizontalAlignment = .left - btnQuickStatus.isEnabled = true - labelQuickStatus.textColor = NCBrandColor.shared.brand - imageDownArrow.image = UIImage(named: "downArrow")?.imageColor(NCBrandColor.shared.brand) - - if NCSharePermissions.canEdit(tableShare.permissions, isDirectory: isDirectory) { // Can edit - labelQuickStatus.text = NSLocalizedString("_share_editing_", comment: "") - } else if tableShare.permissions == NCSharePermissions.permissionReadShare { // Read only - labelQuickStatus.text = NSLocalizedString("_share_read_only_", comment: "") - } else { // Custom permissions - labelQuickStatus.text = NSLocalizedString("_custom_permissions_", comment: "") - } + imagePermissionType.image = imagePermissionType.image?.imageColor(NCBrandColor.shared.shareBlueColor) + updatePermissionUI() + } - let fileName = NCSession.shared.getFileName(urlBase: session.urlBase, user: tableShare.shareWith) - let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) + private func updatePermissionUI() { + guard let tableShare = tableShare else { return } - imageItem.contentMode = .scaleAspectFill + let permissions = NCPermissions() - if tableShare.shareType == NCShareCommon.shareTypeTeam { - imageItem.image = utility.loadImage(named: "custom.person.3.circle.fill", colors: [NCBrandColor.shared.iconImageColor2]) - } else if results.image == nil { - imageItem.image = utility.loadUserImage(for: tableShare.shareWith, displayName: tableShare.shareWithDisplayname, urlBase: metadata.urlBase) + if tableShare.permissions == permissions.permissionCreateShare { + labelQuickStatus.text = NSLocalizedString("_share_quick_permission_everyone_can_just_upload_", comment: "") + imagePermissionType.image = UIImage(named: "upload")?.imageColor(NCBrandColor.shared.shareBlueColor) + } else if permissions.isAnyPermissionToEdit(tableShare.permissions) { + labelQuickStatus.text = NSLocalizedString("_share_quick_permission_everyone_can_edit_", comment: "") + imagePermissionType.image = UIImage(named: "editNew")?.imageColor(NCBrandColor.shared.shareBlueColor) } else { - imageItem.image = results.image + labelQuickStatus.text = NSLocalizedString("_share_quick_permission_everyone_can_only_view_", comment: "") + imagePermissionType.image = UIImage(named: "showPasswordNew")?.imageColor(NCBrandColor.shared.shareBlueColor) } - 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)) - } + imagePasswordSet.isHidden = tableShare.password.isEmpty + imageExpiredDateSet.isHidden = (tableShare.expirationDate == nil) + + leadingContraintofImageRightArrow.constant = (imagePasswordSet.isHidden && imageExpiredDateSet.isHidden) ? 0 : 5 } - private func getTypeString(_ tableShare: tableShareV2) -> String { - switch tableShare.shareType { - case NCShareCommon.shareTypeFederated: - return NSLocalizedString("_remote_", comment: "") - case NCShareCommon.shareTypeFederatedGroup: - return NSLocalizedString("_remote_group_", comment: "") - case NCShareCommon.shareTypeRoom: - return NSLocalizedString("_conversation_", comment: "") - default: - return "" - } - if tableShare.permissions == permissions.permissionCreateShare { - labelQuickStatus.text = NSLocalizedString("_share_file_drop_", comment: "") - } else { - // Read Only - if permissions.isAnyPermissionToEdit(tableShare.permissions) { - labelQuickStatus.text = NSLocalizedString("_share_editing_", comment: "") - } else { - labelQuickStatus.text = NSLocalizedString("_share_read_only_", comment: "") - } - } - } + private func setupCellUI(userId: String) { + guard let tableShare = tableShare else { return } - override func awakeFromNib() { - super.awakeFromNib() - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapAvatarImage(_:))) - imageItem?.addGestureRecognizer(tapGesture) - buttonMenu.setImage(UIImage.init(named: "shareMenu")!.image(color: NCBrandColor.shared.customer, size: 24), for: .normal) - buttonMenu.contentMode = .scaleAspectFill - labelQuickStatus.textColor = NCBrandColor.shared.customer - imageDownArrow.image = UIImage(named: "downArrow")?.imageColor(NCBrandColor.shared.customer) - switchCanEdit.transform = CGAffineTransform(scaleX: 0.75, y: 0.75) - switchCanEdit.onTintColor = NCBrandColor.shared.brandElement - } + let permissions = NCPermissions() + labelTitle.text = tableShare.shareWithDisplayname - @objc func tapAvatarImage(_ sender: UITapGestureRecognizer) { - delegate?.showProfile(with: tableShare, sender: sender) + let isOwner = tableShare.uidOwner == userId || tableShare.uidFileOwner == userId + isUserInteractionEnabled = isOwner + buttonMenu.isHidden = !isOwner + buttonMenu.accessibilityLabel = NSLocalizedString("_more_", comment: "") + + btnQuickStatus.setTitle("", for: .normal) + btnQuickStatus.isEnabled = true + btnQuickStatus.accessibilityHint = NSLocalizedString("_user_sharee_footer_", comment: "") + btnQuickStatus.contentHorizontalAlignment = .left + + setupCellUIAppearance() +// let permissionValue = tableShare.permissions +// +// if permissionValue == permissions.permissionCreateShare { +// labelQuickStatus.text = NSLocalizedString("_share_quick_permission_everyone_can_just_upload_", comment: "") +// imagePermissionType.image = UIImage(named: "upload")?.imageColor(NCBrandColor.shared.shareBlueColor) +// } else if permissions.isAnyPermissionToEdit(permissionValue) { +// labelQuickStatus.text = NSLocalizedString("_share_quick_permission_everyone_can_edit_", comment: "") +// imagePermissionType.image = UIImage(named: "editNew")?.imageColor(NCBrandColor.shared.shareBlueColor) +// } else { +// labelQuickStatus.text = NSLocalizedString("_share_quick_permission_everyone_can_only_view_", comment: "") +// imagePermissionType.image = UIImage(named: "showPasswordNew")?.imageColor(NCBrandColor.shared.shareBlueColor) +// } } + // MARK: - Actions @IBAction func touchUpInsideMenu(_ sender: Any) { delegate?.tapMenu(with: tableShare, sender: sender) } @@ -178,6 +151,10 @@ class NCShareUserCell: UITableViewCell, NCCellProtocol { @IBAction func quickStatusClicked(_ sender: Any) { delegate?.quickStatus(with: tableShare, sender: sender) } + + @objc func tapAvatarImage(_ sender: UITapGestureRecognizer) { + delegate?.showProfile(with: tableShare, sender: sender) + } } protocol NCShareUserCellDelegate: AnyObject { @@ -186,75 +163,51 @@ protocol NCShareUserCellDelegate: AnyObject { func quickStatus(with tableShare: tableShare?, sender: Any) } -// MARK: - NCSearchUserDropDownCell - class NCSearchUserDropDownCell: DropDownCell, NCCellProtocol { @IBOutlet weak var imageItem: UIImageView! @IBOutlet weak var imageStatus: UIImageView! - @IBOutlet weak var status: UILabel! + @IBOutlet weak var statusLabel: UILabel! @IBOutlet weak var imageShareeType: UIImageView! - @IBOutlet weak var centerTitle: NSLayoutConstraint! + @IBOutlet weak var centerTitleConstraint: NSLayoutConstraint! + + private var userIdentifier: String = "" + private var currentIndexPath = IndexPath() - private var user: String = "" - private var index = IndexPath() - private let utilityFileSystem = NCUtilityFileSystem() + // MARK: - NCCellProtocol var indexPath: IndexPath { - get { return index } - set { index = newValue } + get { currentIndexPath } + set { currentIndexPath = newValue } } + var fileAvatarImageView: UIImageView? { - return imageItem + imageItem } + var fileUser: String? { - get { return user } - set { user = newValue ?? "" } + get { userIdentifier } + set { userIdentifier = newValue ?? "" } } + // MARK: - Setup + func setupCell(sharee: NKSharee, session: NCSession.Session) { let utility = NCUtility() - imageShareeType.image = NCShareCommon().getImageShareType(shareType: sharee.shareType, isDropDown: true) - - let status = utility.getUserStatus(userIcon: sharee.userIcon, userStatus: sharee.userStatus, userMessage: sharee.userMessage) - imageStatus.image = status.statusImage - self.status.text = status.statusMessage - if self.status.text?.count ?? 0 > 0 { - centerTitle.constant = -5 - } else { - centerTitle.constant = 0 - } + let shareCommon = NCShareCommon() + + imageShareeType.image = shareCommon.getImageShareType(shareType: sharee.shareType, isDropDown: true) - imageItem.image = utility.loadUserImage(for: sharee.shareWith, displayName: nil, urlBase: session.urlBase) - - let fileName = NCSession.shared.getFileName(urlBase: session.urlBase, user: sharee.shareWith) - let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) - - if results.image == nil { - let etag = NCManageDatabase.shared.getTableAvatar(fileName: fileName)?.etag - let fileNameLocalPath = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName) - - NextcloudKit.shared.downloadAvatar( - user: sharee.shareWith, - fileNameLocalPath: fileNameLocalPath, - sizeImage: NCGlobal.shared.avatarSize, - avatarSizeRounded: NCGlobal.shared.avatarSizeRounded, - etagResource: etag, - account: session.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: session.account, - path: sharee.shareWith, - name: "downloadAvatar") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { _, imageAvatar, _, etag, _, error in - if error == .success, let etag = etag, let imageAvatar = imageAvatar { - NCManageDatabase.shared.addAvatar(fileName: fileName, etag: etag) - self.imageItem.image = imageAvatar - } else if error.errorCode == NCGlobal.shared.errorNotModified, let imageAvatar = NCManageDatabase.shared.setAvatarLoaded(fileName: fileName) { - self.imageItem.image = imageAvatar - } - } + let userStatus = utility.getUserStatus(userIcon: sharee.userIcon, + userStatus: sharee.userStatus, + userMessage: sharee.userMessage) + + if let statusImage = userStatus.statusImage { + imageStatus.image = statusImage + imageStatus.makeCircularBackground(withColor: .systemBackground) } + + statusLabel.text = userStatus.statusMessage + centerTitleConstraint.constant = (statusLabel.text?.isEmpty == false) ? -5 : 0 } } diff --git a/iOSClient/Share/NoSharesFooterView.swift b/iOSClient/Share/NoSharesFooterView.swift new file mode 100644 index 0000000000..636b319a83 --- /dev/null +++ b/iOSClient/Share/NoSharesFooterView.swift @@ -0,0 +1,63 @@ +// +// NoSharesFooterView.swift +// Nextcloud +// +// Created by A106551118 on 12/08/25. +// Copyright © 2025 Marino Faggiana. All rights reserved. +// + + +import UIKit + +class NoSharesFooterView: UITableViewHeaderFooterView { + + static let reuseIdentifier = "NoSharesFooterView" + + private let titleLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 17, weight: .semibold) + label.textColor = NCBrandColor.shared.textColor + label.text = NSLocalizedString("_share_shared_with_", comment: "") + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private let infoLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 15, weight: .regular) + label.textColor = NCBrandColor.shared.textColor + label.text = NSLocalizedString("_share_no_shares_text_", comment: "") + label.numberOfLines = 0 + label.lineBreakMode = .byWordWrapping + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + override init(reuseIdentifier: String?) { + super.init(reuseIdentifier: reuseIdentifier) + setupUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupUI() + } + + private func setupUI() { + contentView.backgroundColor = .clear + contentView.addSubview(titleLabel) + contentView.addSubview(infoLabel) + + NSLayoutConstraint.activate([ + titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20), + titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15), + titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15), + titleLabel.heightAnchor.constraint(equalToConstant: 25), + + infoLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 15), + infoLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15), + infoLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15), + infoLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + ]) + } +} diff --git a/iOSClient/Share/ShareDownloadLimitNetwork.swift b/iOSClient/Share/ShareDownloadLimitNetwork.swift index 9fadc08515..118bd538d0 100644 --- a/iOSClient/Share/ShareDownloadLimitNetwork.swift +++ b/iOSClient/Share/ShareDownloadLimitNetwork.swift @@ -22,9 +22,13 @@ class NMCCommunication: NSObject, XMLParserDelegate { var foundCharacters = ""; var downloadLimit = DownloadLimit() private lazy var appDelegate = UIApplication.shared.delegate as? AppDelegate + var controller: NCMainTabBarController! + var session: NCSession.Session { + NCSession.shared.getSession(controller: controller) + } func getDownloadLimit(token: String, completion: @escaping (_ downloadLimit: DownloadLimit?, _ errorDescription: String) -> Void) { - let baseUrl = appDelegate?.urlBase ?? "" // NCBrandOptions.shared.loginBaseUrl + let baseUrl = session.urlBase // NCBrandOptions.shared.loginBaseUrl let endPoint = "/ocs/v2.php/apps/files_downloadlimit/\(token)/limit" let path = baseUrl+endPoint do { @@ -62,7 +66,7 @@ class NMCCommunication: NSObject, XMLParserDelegate { } func setDownloadLimit(deleteLimit: Bool, limit: String, token: String, completion: @escaping (_ success: Bool, _ errorDescription: String) -> Void) { - let baseUrl = appDelegate?.urlBase ?? "" //NCBrandOptions.shared.loginBaseUrl + let baseUrl = session.urlBase //NCBrandOptions.shared.loginBaseUrl let endPoint = "/ocs/v2.php/apps/files_downloadlimit/\(token)/limit" let path = baseUrl+endPoint do { @@ -108,8 +112,8 @@ class NMCCommunication: NSObject, XMLParserDelegate { } public func authorizationToken() -> String { - let accountDetails = NCManageDatabase.shared.getAllAccount().first - let password = NCKeychain().getPassword(account: accountDetails?.account ?? "") + let accountDetails = NCManageDatabase.shared.getAllTableAccount().first + let password = NCKeychain().getPassword(account: accountDetails?.account ?? "") let username = accountDetails?.user ?? "" let credential = Data("\(username):\(password)".utf8).base64EncodedString() return ("Basic \(credential)") @@ -140,7 +144,7 @@ class NMCCommunication: NSObject, XMLParserDelegate { } } -struct DownloadLimit: Codable { - var limit: Int? - var count: Int? -} +//struct DownloadLimit: Codable { +// var limit: Int? +// var count: Int? +//} diff --git a/iOSClient/Share/Shareable.swift b/iOSClient/Share/Shareable.swift index 374eebabb1..4b1f413aeb 100644 --- a/iOSClient/Share/Shareable.swift +++ b/iOSClient/Share/Shareable.swift @@ -16,11 +16,9 @@ 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 } - var itemType: String { get set } } // MARK: - Default Implementations @@ -31,7 +29,7 @@ extension Shareable { /// var formattedDateString: String? { guard let date = expirationDate else { - return nil + return "" } let dateFormatter = DateFormatter() @@ -56,44 +54,8 @@ extension Shareable { // MARK: - tableShare Extension -extension tableShare: Shareable { - var downloadAndSync: Bool { - get { - NCManageDatabase.shared.isAttributeDownloadEnabled(attributes: attributes) - } - set { - attributes = NCManageDatabase.shared.setAttibuteDownload(state: newValue) - } - } -} +extension tableShare: Shareable {} // MARK: - NKShare Extension -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 -} +extension NKShare: Shareable {} diff --git a/iOSClient/Share/TransientShare.swift b/iOSClient/Share/TransientShare.swift index 666b91b66c..33c8b0c30d 100644 --- a/iOSClient/Share/TransientShare.swift +++ b/iOSClient/Share/TransientShare.swift @@ -22,24 +22,18 @@ class TransientShare: Shareable { var note: String = "" var expirationDate: NSDate? var shareWithDisplayname: String = "" - var downloadAndSync = false - var itemType: String = "" var attributes: String? private init(shareType: Int, metadata: tableMetadata, password: String?) { - let capabilities = NCNetworking.shared.capabilities[metadata.account] ?? NKCapabilities.Capabilities() - - if metadata.e2eEncrypted, capabilities.e2EEApiVersion == NCGlobal.shared.e2eeVersionV12 { - self.permissions = NCSharePermissions.permissionCreateShare + if metadata.e2eEncrypted, NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV12 { + self.permissions = NCPermissions().permissionCreateShare } else { - self.permissions = capabilities.fileSharingDefaultPermission & metadata.sharePermissionsCollaborationServices + self.permissions = NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityFileSharingDefaultPermission & metadata.sharePermissionsCollaborationServices } self.shareType = shareType - self.itemType = metadata.isDirectory ? NCShareCommon.itemTypeFolder : NCShareCommon.itemTypeFile - if let password = password { self.password = password } @@ -48,9 +42,10 @@ class TransientShare: Shareable { convenience init(sharee: NKSharee, metadata: tableMetadata, password: String?) { self.init(shareType: sharee.shareType, metadata: metadata, password: password) self.shareWith = sharee.shareWith + self.shareWithDisplayname = sharee.label } static func shareLink(metadata: tableMetadata, password: String?) -> TransientShare { - TransientShare(shareType: NCShareCommon.shareTypeLink, metadata: metadata, password: password) + TransientShare(shareType: NCShareCommon().SHARE_TYPE_LINK, metadata: metadata, password: password) } } diff --git a/iOSClient/Utility/NCUtility.swift b/iOSClient/Utility/NCUtility.swift index 562a82c2f7..e557d4587c 100644 --- a/iOSClient/Utility/NCUtility.swift +++ b/iOSClient/Utility/NCUtility.swift @@ -27,6 +27,7 @@ import PDFKit import Accelerate import CoreMedia import Photos +import Alamofire final class NCUtility: NSObject, Sendable { let utilityFileSystem = NCUtilityFileSystem() @@ -47,23 +48,19 @@ final class NCUtility: NSObject, Sendable { } func isTypeFileRichDocument(_ metadata: tableMetadata) -> Bool { + guard metadata.fileNameView != "." else { return false } let fileExtension = (metadata.fileNameView as NSString).pathExtension - guard let capabilities = NCNetworking.shared.capabilities[metadata.account], - !fileExtension.isEmpty, - let mimeType = UTType(tag: fileExtension.uppercased(), tagClass: .filenameExtension, conformingTo: nil)?.identifier else { - return false - } - + guard !fileExtension.isEmpty else { return false } + guard let mimeType = UTType(tag: fileExtension.uppercased(), tagClass: .filenameExtension, conformingTo: nil)?.identifier else { return false } /// contentype - if !capabilities.richDocumentsMimetypes.filter({ $0.contains(metadata.contentType) || $0.contains("text/plain") }).isEmpty { + if !NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityRichDocumentsMimetypes.filter({ $0.contains(metadata.contentType) || $0.contains("text/plain") }).isEmpty { return true } - /// mimetype - if !capabilities.richDocumentsMimetypes.isEmpty && mimeType.components(separatedBy: ".").count > 2 { + if !NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityRichDocumentsMimetypes.isEmpty && mimeType.components(separatedBy: ".").count > 2 { let mimeTypeArray = mimeType.components(separatedBy: ".") let mimeType = mimeTypeArray[mimeTypeArray.count - 2] + "." + mimeTypeArray[mimeTypeArray.count - 1] - if !capabilities.richDocumentsMimetypes.filter({ $0.contains(mimeType) }).isEmpty { + if !NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityRichDocumentsMimetypes.filter({ $0.contains(mimeType) }).isEmpty { return true } } @@ -71,32 +68,39 @@ final class NCUtility: NSObject, Sendable { } func editorsDirectEditing(account: String, contentType: String) -> [String] { - var names: [String] = [] - let capabilities = NCNetworking.shared.capabilities[account] + var editor: [String] = [] + guard let results = NCManageDatabase.shared.getDirectEditingEditors(account: account) else { return editor } - capabilities?.directEditingEditors.forEach { editor in - editor.mimetypes.forEach { mimetype in + for result: tableDirectEditingEditors in results { + for mimetype in result.mimetypes { if mimetype == contentType { - names.append(editor.name) + editor.append(result.editor) } // HARDCODE // https://github.com/nextcloud/text/issues/913 if mimetype == "text/markdown" && contentType == "text/x-markdown" { - names.append(editor.name) + editor.append(result.editor) } if contentType == "text/html" { - names.append(editor.name) + editor.append(result.editor) } } - - editor.optionalMimetypes.forEach { mimetype in + for mimetype in result.optionalMimetypes { if mimetype == contentType { - names.append(editor.name) + editor.append(result.editor) } } } + return Array(Set(editor)) + } - return Array(Set(names)) + func permissionsContainsString(_ metadataPermissions: String, permissions: String) -> Bool { + for char in permissions { + if metadataPermissions.contains(char) == false { + return false + } + } + return true } func getCustomUserAgentNCText() -> String { @@ -133,19 +137,15 @@ final class NCUtility: NSObject, Sendable { return String(intFileId) } - func getVersionBuild() -> String { - if let dictionary = Bundle.main.infoDictionary, - let version = dictionary["CFBundleShortVersionString"], - let build = dictionary["CFBundleVersion"] { - return "\(version).\(build)" - } - return "" - } - - func getVersionMaintenance() -> String { - if let dictionary = Bundle.main.infoDictionary, - let version = dictionary["CFBundleShortVersionString"] { - return "\(version)" + @objc func getVersionApp(withBuild: Bool = true) -> String { + if let dictionary = Bundle.main.infoDictionary { + if let version = dictionary["CFBundleShortVersionString"], let build = dictionary["CFBundleVersion"] { + if withBuild { + return "\(version).\(build)" + } else { + return "\(version)" + } + } } return "" } @@ -283,14 +283,6 @@ final class NCUtility: NSObject, Sendable { return (usedmegabytes, totalmegabytes) } -// func removeForbiddenCharacters(_ fileName: String) -> String { -// var fileName = fileName -// for character in global.forbiddenCharacters { -// fileName = fileName.replacingOccurrences(of: character, with: "") -// } -// return fileName -// } - func getHeightHeaderEmptyData(view: UIView, portraitOffset: CGFloat, landscapeOffset: CGFloat, isHeaderMenuTransferViewEnabled: Bool = false) -> CGFloat { var height: CGFloat = 0 if UIDevice.current.orientation.isPortrait { @@ -300,11 +292,78 @@ final class NCUtility: NSObject, Sendable { } return height } - + + // E-mail validations + // 1. Basic Email Validator (ASCII only) func isValidEmail(_ email: String) -> Bool { + let emailRegex = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}$" + let predicate = NSPredicate(format: "SELF MATCHES[c] %@", emailRegex) + return predicate.evaluate(with: email) + } + + // 2. Manually Convert Unicode Domain to Punycode with German Char Support + func convertToPunycode(email: String) -> String? { + guard let atIndex = email.firstIndex(of: "@") else { return nil } + + let localPart = String(email[.. String? { + // Mapping of common German characters to their corresponding Punycode equivalents + var punycodeDomain = domain.lowercased() + + let germanCharToPunycode: [String: String] = [ + "ü": "xn--u-1fa", // ü → xn--u-1fa + "ä": "xn--a-1fa", // ä → xn--a-1fa + "ö": "xn--o-1fa", // ö → xn--o-1fa + "ß": "xn--ss-1fa", // ß → xn--ss-1fa + "é": "xn--e-1fa", // é → xn--e-1fa + "è": "xn--e-1f", // è → xn--e-1f + "à": "xn--a-1f", // à → xn--a-1f + ] + + // Replace each German character with the corresponding Punycode equivalent + for (char, punycode) in germanCharToPunycode { + punycodeDomain = punycodeDomain.replacingOccurrences(of: char, with: punycode) + } + + // If no change occurred, return the domain as it is (i.e., no Punycode needed) + return punycodeDomain + } + + // 4. IDN Email Validator (handles Unicode domain by converting to Punycode) + func isValidIDNEmail(_ email: String) -> Bool { + // Convert domain part to Punycode and validate using basic email regex + guard let punycodeEmail = convertToPunycode(email: email) else { + return false + } + + return isValidEmail(punycodeEmail) + } + + // 5. Unified Email Validation - Check for both basic and IDN emails + func validateEmail(_ email: String) -> Bool { + if isValidEmail(email) { + print("Valid ASCII email: \(email)") + return true + } else if isValidIDNEmail(email) { + print("Valid IDN email: \(email)") + return true + } else { + print("Invalid email: \(email)") + return false + } } } From ca0399d973a3ff8e53b478d6d4f65e1a45b26e8c Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:49:41 +0530 Subject: [PATCH 006/177] NMC 1933 - Collabora customisation changes --- .../CollaboraTestCase.swift | 142 +++++ iOSClient/BrowserWeb/NCBrowserWeb.swift | 2 +- .../Data/NCManageDatabase+Metadata.swift | 337 ++++++++++- .../NCCreateFormUploadDocuments.storyboard | 149 +++++ .../NCCreateFormUploadDocuments.swift | 556 ++++++++++++++++++ .../FolderPathCustomCell.swift | 33 ++ .../NMC Custom Views/FolderPathCustomCell.xib | 74 +++ .../NCCreateDocumentCustomTextField.swift | 70 +++ .../NCCreateDocumentCustomTextField.xib | 80 +++ 9 files changed, 1441 insertions(+), 2 deletions(-) create mode 100644 Tests/NextcloudUnitTests/CollaboraTestCase.swift create mode 100644 iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.storyboard create mode 100644 iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift create mode 100644 iOSClient/NMC Custom Views/FolderPathCustomCell.swift create mode 100644 iOSClient/NMC Custom Views/FolderPathCustomCell.xib create mode 100644 iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift create mode 100644 iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.xib diff --git a/Tests/NextcloudUnitTests/CollaboraTestCase.swift b/Tests/NextcloudUnitTests/CollaboraTestCase.swift new file mode 100644 index 0000000000..51b3ecce1f --- /dev/null +++ b/Tests/NextcloudUnitTests/CollaboraTestCase.swift @@ -0,0 +1,142 @@ +// +// CollaboraTestCase.swift +// NextcloudTests +// +// Created by A200073704 on 06/05/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +@testable import Nextcloud +import XCTest +import NextcloudKit + +class CollaboraTestCase: XCTestCase { + + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testCollaboraDocumentIsPresent() { + + var viewForDocument: NCMenuAction? + + if let image = UIImage(named: "create_file_document") { + viewForDocument = NCMenuAction(title: NSLocalizedString("_create_new_document_", comment: ""), icon: image, action: { _ in + guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() as? UINavigationController else { + return + } + + let viewController = navigationController.topViewController as? NCCreateFormUploadDocuments + viewController?.titleForm = NSLocalizedString("_create_new_document_", comment: "") + }) + } + + XCTAssertNotNil(viewForDocument) + + } + + func testCollaboraPresentationIsPresent() { + + var viewForPresentation: NCMenuAction? + + if let image = UIImage(named: "create_file_ppt") { + viewForPresentation = NCMenuAction(title: NSLocalizedString("_create_new_presentation_", comment: ""), icon: image, action: { _ in + guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() as? UINavigationController else { + return + } + + let viewController = navigationController.topViewController as? NCCreateFormUploadDocuments + viewController?.titleForm = NSLocalizedString("_create_new_presentation_", comment: "") + }) + } + + XCTAssertNotNil(viewForPresentation) + + } + + func testCollaboraSpreadsheetIsPresent() { + + var viewForSpreadsheet: NCMenuAction? + + if let image = UIImage(named: "create_file_xls") { + viewForSpreadsheet = NCMenuAction(title: NSLocalizedString("_create_new_spreadsheet_", comment: ""), icon: image, action: { _ in + guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() as? UINavigationController else { + return + } + + let viewController = navigationController.topViewController as? NCCreateFormUploadDocuments + viewController?.titleForm = NSLocalizedString("_create_new_spreadsheet_", comment: "") + }) + } + + XCTAssertNotNil(viewForSpreadsheet) + + } + + func testTextDocumentIsPresent() { + + var textMenu: NCMenuAction? + + if let image = UIImage(named: "file_txt_menu") { + textMenu = NCMenuAction(title: NSLocalizedString("_create_nextcloudtext_document_", comment: ""), icon: image, action: { _ in + guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() as? UINavigationController else { + return + } + + let viewController = navigationController.topViewController as? NCCreateFormUploadDocuments + viewController?.titleForm = NSLocalizedString("_create_nextcloudtext_document_", comment: "") + }) + } + + XCTAssertNotNil(textMenu) + + } + + func testTextDocumentAction() { + + let text = NCGlobal.shared.actionTextDocument + XCTAssertNotNil(text, "Text Editor Should be opened") + } + + func testTextFieldIsPresent() { + + let storyboard = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil) + guard let viewController = storyboard.instantiateInitialViewController() as? NCCreateFormUploadDocuments else { + return + } + + // Verify that a text field is present in the view controller + let textFields = viewController.view.subviews.filter { $0 is UITextField } + XCTAssertFalse(textFields.isEmpty, "No text field found in NCCreateFormUploadDocuments") + } + + func testSavePathFolder() { + + let viewController = NCCreateFormUploadDocuments() + + let form : XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor + form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow + + var row : XLFormRowDescriptor + + // the section with the title "Folder Destination" + + row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "kNMCFolderCustomCellType", title: "") + row.action.formSelector = #selector(viewController.changeDestinationFolder(_:)) + + // Verify that section was found + XCTAssertNotNil(row, "Expected save path section to exist in form.") + + } + + + + + + +} diff --git a/iOSClient/BrowserWeb/NCBrowserWeb.swift b/iOSClient/BrowserWeb/NCBrowserWeb.swift index 206197c76c..4bf4a4f613 100644 --- a/iOSClient/BrowserWeb/NCBrowserWeb.swift +++ b/iOSClient/BrowserWeb/NCBrowserWeb.swift @@ -36,7 +36,7 @@ class NCBrowserWeb: UIViewController { buttonExit.isHidden = true } else { self.view.bringSubviewToFront(buttonExit) - let image = NCUtility().loadImage(named: "xmark", colors: [.systemBlue]) + let image = NCUtility().loadImage(named: "xmark", colors: [NCBrandColor.shared.customer]) buttonExit.setImage(image, for: .normal) } diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index 62a225a578..c85e133fe9 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -318,6 +318,7 @@ extension tableMetadata { return false } if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE) { +// if !NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingApiEnabled || (NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEEnabled && isDirectoryE2EE), !e2eEncrypted { return false } return true @@ -343,6 +344,229 @@ extension tableMetadata { } extension NCManageDatabase { + + // MARK: - Create Metadata + + func convertFileToMetadata(_ file: NKFile, isDirectoryE2EE: Bool) -> tableMetadata { + let metadata = tableMetadata() + + metadata.account = file.account + metadata.checksums = file.checksums + metadata.commentsUnread = file.commentsUnread + metadata.contentType = file.contentType + if let date = file.creationDate { + metadata.creationDate = date as NSDate + } else { + metadata.creationDate = file.date as NSDate + } + metadata.dataFingerprint = file.dataFingerprint + metadata.date = file.date as NSDate + if let datePhotosOriginal = file.datePhotosOriginal { + metadata.datePhotosOriginal = datePhotosOriginal as NSDate + } else { + metadata.datePhotosOriginal = metadata.date + } + metadata.directory = file.directory + metadata.downloadURL = file.downloadURL + metadata.e2eEncrypted = file.e2eEncrypted + metadata.etag = file.etag + for dict in file.exifPhotos { + for (key, value) in dict { + let keyValue = NCKeyValue() + keyValue.key = key + keyValue.value = value + metadata.exifPhotos.append(keyValue) + } + } + metadata.favorite = file.favorite + metadata.fileId = file.fileId + metadata.fileName = file.fileName + metadata.fileNameView = file.fileName + metadata.hasPreview = file.hasPreview + metadata.hidden = file.hidden + switch (file.fileName as NSString).pathExtension { + case "odg": + metadata.iconName = "diagram" + case "csv", "xlsm" : + metadata.iconName = "file_xls" + default: + metadata.iconName = file.iconName + } + metadata.mountType = file.mountType + metadata.name = file.name + metadata.note = file.note + metadata.ocId = file.ocId + metadata.ocIdTransfer = file.ocId + metadata.ownerId = file.ownerId + metadata.ownerDisplayName = file.ownerDisplayName + metadata.lock = file.lock + metadata.lockOwner = file.lockOwner + metadata.lockOwnerEditor = file.lockOwnerEditor + metadata.lockOwnerType = file.lockOwnerType + metadata.lockOwnerDisplayName = file.lockOwnerDisplayName + metadata.lockTime = file.lockTime + metadata.lockTimeOut = file.lockTimeOut + metadata.path = file.path + metadata.permissions = file.permissions + metadata.placePhotos = file.placePhotos + metadata.quotaUsedBytes = file.quotaUsedBytes + metadata.quotaAvailableBytes = file.quotaAvailableBytes + metadata.richWorkspace = file.richWorkspace + metadata.resourceType = file.resourceType + metadata.serverUrl = file.serverUrl + metadata.serveUrlFileName = file.serverUrl + "/" + file.fileName + metadata.sharePermissionsCollaborationServices = file.sharePermissionsCollaborationServices + + for element in file.shareType { + metadata.shareType.append(element) + } + for element in file.tags { + metadata.tags.append(element) + } + metadata.size = file.size + metadata.classFile = file.classFile + // iOS 12.0,* don't detect UTI text/markdown, text/x-markdown + if (metadata.contentType == "text/markdown" || metadata.contentType == "text/x-markdown") && metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue { + metadata.classFile = NKCommon.TypeClassFile.document.rawValue + } + if let date = file.uploadDate { + metadata.uploadDate = date as NSDate + } else { + metadata.uploadDate = file.date as NSDate + } + metadata.urlBase = file.urlBase + metadata.user = file.user + metadata.userId = file.userId + metadata.latitude = file.latitude + metadata.longitude = file.longitude + metadata.altitude = file.altitude + metadata.height = Int(file.height) + metadata.width = Int(file.width) + metadata.livePhotoFile = file.livePhotoFile + metadata.isFlaggedAsLivePhotoByServer = file.isFlaggedAsLivePhotoByServer + + // E2EE find the fileName for fileNameView + if isDirectoryE2EE || file.e2eEncrypted { + if let tableE2eEncryption = getE2eEncryption(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameIdentifier == %@", file.account, file.serverUrl, file.fileName)) { + metadata.fileNameView = tableE2eEncryption.fileName + let results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: metadata.fileNameView, mimeType: file.contentType, directory: file.directory, account: file.account) + metadata.contentType = results.mimeType + metadata.iconName = results.iconName + metadata.classFile = results.classFile + } + } + return metadata + } + + func convertFilesToMetadatas(_ files: [NKFile], useFirstAsMetadataFolder: Bool, completion: @escaping (_ metadataFolder: tableMetadata, _ metadatas: [tableMetadata]) -> Void) { + var counter: Int = 0 + var isDirectoryE2EE: Bool = false + let listServerUrl = ThreadSafeDictionary() + var metadataFolder = tableMetadata() + var metadatas: [tableMetadata] = [] + + for file in files { + if let key = listServerUrl[file.serverUrl] { + isDirectoryE2EE = key + } else { + isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file) + listServerUrl[file.serverUrl] = isDirectoryE2EE + } + + let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) + + if counter == 0 && useFirstAsMetadataFolder { + metadataFolder = metadata + } else { + metadatas.append(metadata) + } + + counter += 1 + } + completion(tableMetadata(value: metadataFolder), metadatas) + } + + func convertFilesToMetadatasAsync(_ files: [NKFile], useFirstAsMetadataFolder: Bool) async -> (metadataFolder: tableMetadata, metadatas: [tableMetadata]) { + await withCheckedContinuation { continuation in + convertFilesToMetadatas(files, useFirstAsMetadataFolder: useFirstAsMetadataFolder) { metadataFolder, metadatas in + continuation.resume(returning: (metadataFolder, metadatas)) + } + } + } + + func getMetadataDirectoryFrom(files: [NKFile]) -> tableMetadata? { + guard let file = files.first else { return nil } + let isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file) + let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) + + return metadata + } + + func convertFilesToMetadatas(_ files: [NKFile], useFirstAsMetadataFolder: Bool) async -> (metadataFolder: tableMetadata, metadatas: [tableMetadata]) { + await withUnsafeContinuation({ continuation in + convertFilesToMetadatas(files, useFirstAsMetadataFolder: useFirstAsMetadataFolder) { metadataFolder, metadatas in + continuation.resume(returning: (metadataFolder, metadatas)) + } + }) + } + + func createMetadata(fileName: String, fileNameView: String, ocId: String, serverUrl: String, url: String, contentType: String, isUrl: Bool = false, name: String = NCGlobal.shared.appName, subline: String? = nil, iconName: String? = nil, iconUrl: String? = nil, directory: Bool = false, session: NCSession.Session, sceneIdentifier: String?) -> tableMetadata { + let metadata = tableMetadata() + + if isUrl { + metadata.contentType = "text/uri-list" + if let iconName = iconName { + metadata.iconName = iconName + } else { + metadata.iconName = NKCommon.TypeClassFile.url.rawValue + } + metadata.classFile = NKCommon.TypeClassFile.url.rawValue + } else { + let (mimeType, classFile, iconName, _, _, _) = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileName, mimeType: contentType, directory: directory, account: session.account) + metadata.contentType = mimeType + metadata.iconName = iconName + metadata.classFile = classFile + // iOS 12.0,* don't detect UTI text/markdown, text/x-markdown + if classFile == NKCommon.TypeClassFile.unknow.rawValue && (mimeType == "text/x-markdown" || mimeType == "text/markdown") { + metadata.iconName = NKCommon.TypeIconFile.txt.rawValue + metadata.classFile = NKCommon.TypeClassFile.document.rawValue + } + } + if let iconUrl = iconUrl { + metadata.iconUrl = iconUrl + } + + let fileName = fileName.trimmingCharacters(in: .whitespacesAndNewlines) + + metadata.account = session.account + metadata.creationDate = Date() as NSDate + metadata.date = Date() as NSDate + metadata.directory = directory + metadata.hasPreview = true + metadata.etag = ocId + metadata.fileName = fileName + metadata.fileNameView = fileName + metadata.name = name + metadata.ocId = ocId + metadata.ocIdTransfer = ocId + metadata.permissions = "RGDNVW" + metadata.serverUrl = serverUrl + metadata.serveUrlFileName = serverUrl + "/" + fileName + metadata.subline = subline + metadata.uploadDate = Date() as NSDate + metadata.url = url + metadata.urlBase = session.urlBase + metadata.user = session.user + metadata.userId = session.userId + metadata.sceneIdentifier = sceneIdentifier + metadata.nativeFormat = !NCKeychain().formatCompatibility + + if !metadata.urlBase.isEmpty, metadata.serverUrl.hasPrefix(metadata.urlBase) { + metadata.path = String(metadata.serverUrl.dropFirst(metadata.urlBase.count)) + "/" + } + return metadata + } + func isMetadataShareOrMounted(metadata: tableMetadata, metadataFolder: tableMetadata?) -> Bool { var isShare = false var isMounted = false @@ -1318,7 +1542,7 @@ extension NCManageDatabase { guard let decodedBaseUrl = baseUrl.removingPercentEncoding else { return nil } - + return performRealmRead { realm in let object = realm.objects(tableMetadata.self) .filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, decodedBaseUrl, fileName) @@ -1326,4 +1550,115 @@ extension NCManageDatabase { return object?.detachedCopy() } } + + func createMetadatasFolder(assets: [PHAsset], + useSubFolder: Bool, + session: NCSession.Session, completion: @escaping ([tableMetadata]) -> Void) { + var foldersCreated: Set = [] + var metadatas: [tableMetadata] = [] + let serverUrlBase = getAccountAutoUploadDirectory(session: session) + let fileNameBase = getAccountAutoUploadFileName(account: session.account) + let predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND directory == true", session.account, serverUrlBase) + + func createMetadata(serverUrl: String, fileName: String, metadata: tableMetadata?) { + guard !foldersCreated.contains(serverUrl + "/" + fileName) else { + return + } + foldersCreated.insert(serverUrl + "/" + fileName) + + if let metadata { + metadata.status = NCGlobal.shared.metadataStatusWaitCreateFolder + metadata.sessionSelector = NCGlobal.shared.selectorUploadAutoUpload + metadata.sessionDate = Date() + metadatas.append(tableMetadata(value: metadata)) + } else { + let metadata = NCManageDatabase.shared.createMetadata(fileName: fileName, + fileNameView: fileName, + ocId: NSUUID().uuidString, + serverUrl: serverUrl, + url: "", + contentType: "httpd/unix-directory", + directory: true, + session: session, + sceneIdentifier: nil) + metadata.status = NCGlobal.shared.metadataStatusWaitCreateFolder + metadata.sessionSelector = NCGlobal.shared.selectorUploadAutoUpload + metadata.sessionDate = Date() + metadatas.append(metadata) + } + } + + let metadatasFolder = getMetadatas(predicate: predicate) + let targetPath = serverUrlBase + "/" + fileNameBase + let metadata = metadatasFolder.first(where: { $0.serverUrl + "/" + $0.fileNameView == targetPath }) + createMetadata(serverUrl: serverUrlBase, fileName: fileNameBase, metadata: metadata) + + if useSubFolder { + let autoUploadServerUrlBase = self.getAccountAutoUploadServerUrlBase(session: session) + let autoUploadSubfolderGranularity = self.getAccountAutoUploadSubfolderGranularity() + let folders = Set(assets.map { self.utilityFileSystem.createGranularityPath(asset: $0) }).sorted() + + for folder in folders { + let componentsDate = folder.split(separator: "/") + let year = componentsDate[0] + let serverUrl = autoUploadServerUrlBase + let fileName = String(year) + let targetPath = serverUrl + "/" + fileName + let metadata = metadatasFolder.first(where: { $0.serverUrl + "/" + $0.fileNameView == targetPath }) + + createMetadata(serverUrl: serverUrl, fileName: fileName, metadata: metadata) + + if autoUploadSubfolderGranularity >= NCGlobal.shared.subfolderGranularityMonthly { + let month = componentsDate[1] + let serverUrl = autoUploadServerUrlBase + "/" + year + let fileName = String(month) + let targetPath = serverUrl + "/" + fileName + let metadata = metadatasFolder.first(where: { $0.serverUrl + "/" + $0.fileNameView == targetPath }) + + createMetadata(serverUrl: serverUrl, fileName: fileName, metadata: metadata) + + if autoUploadSubfolderGranularity == NCGlobal.shared.subfolderGranularityDaily { + let day = componentsDate[2] + let serverUrl = autoUploadServerUrlBase + "/" + year + "/" + month + let fileName = String(day) + let targetPath = serverUrl + "/" + fileName + let metadata = metadatasFolder.first(where: { $0.serverUrl + "/" + $0.fileNameView == targetPath }) + + createMetadata(serverUrl: serverUrl, fileName: fileName, metadata: metadata) + } + } + } + completion(metadatas) + } else { + completion(metadatas) + } + } + + func getMediaMetadatas(predicate: NSPredicate, sorted: String? = nil, ascending: Bool = false) -> ThreadSafeArray? { + + do { + let realm = try Realm() + if let sorted { + var results: [tableMetadata] = [] + switch NCKeychain().mediaSortDate { + case "date": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.date as Date) > ($1.date as Date) } + case "creationDate": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.creationDate as Date) > ($1.creationDate as Date) } + case "uploadDate": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.uploadDate as Date) > ($1.uploadDate as Date) } + default: + let results = realm.objects(tableMetadata.self).filter(predicate) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } else { + let results = realm.objects(tableMetadata.self).filter(predicate) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") + } + return nil + } } diff --git a/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.storyboard b/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.storyboard new file mode 100644 index 0000000000..6995ac52d9 --- /dev/null +++ b/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.storyboard @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift b/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift new file mode 100644 index 0000000000..61177fa174 --- /dev/null +++ b/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift @@ -0,0 +1,556 @@ +// +// NCCreateFormUploadDocuments.swift +// Nextcloud +// +// Created by Marino Faggiana on 14/11/18. +// Copyright © 2018 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +import UIKit +import NextcloudKit +import XLForm + +// MARK: - + +@objc class NCCreateFormUploadDocuments: XLFormViewController, NCSelectDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, NCCreateFormUploadConflictDelegate { + + @IBOutlet weak var indicator: UIActivityIndicatorView! + @IBOutlet weak var collectionView: UICollectionView! + @IBOutlet weak var collectionViewHeigth: NSLayoutConstraint! + + let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! + var editorId = "" + var creatorId = "" + var typeTemplate = "" + var templateIdentifier = "" + var serverUrl = "" + var fileNameFolder = "" + var fileName = "" + var fileNameExtension = "" + var titleForm = "" + var listOfTemplate: [NKEditorTemplates] = [] + var selectTemplate: NKEditorTemplates? + let utilityFileSystem = NCUtilityFileSystem() + let utility = NCUtility() + + // Layout + let numItems = 2 + let sectionInsets: CGFloat = 10 + let highLabelName: CGFloat = 20 + + // MARK: - View Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + + if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) { + fileNameFolder = "/" + } else { + fileNameFolder = (serverUrl as NSString).lastPathComponent + } + + self.tableView.separatorStyle = UITableViewCell.SeparatorStyle.none + + view.backgroundColor = .systemGroupedBackground + collectionView.backgroundColor = .systemGroupedBackground + tableView.backgroundColor = .secondarySystemGroupedBackground + + let cancelButton: UIBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: UIBarButtonItem.Style.plain, target: self, action: #selector(cancel)) + let saveButton: UIBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_save_", comment: ""), style: UIBarButtonItem.Style.plain, target: self, action: #selector(save)) + cancelButton.tintColor = NCBrandColor.shared.brand + saveButton.tintColor = NCBrandColor.shared.brand + + self.navigationItem.leftBarButtonItem = cancelButton + self.navigationItem.rightBarButtonItem = saveButton + self.navigationItem.rightBarButtonItem?.isEnabled = false + + // title + self.title = titleForm + + fileName = NCUtilityFileSystem().createFileNameDate("Text", ext: getFileExtension()) + + initializeForm() + getTemplate() + } + + // MARK: - Tableview (XLForm) + + func initializeForm() { + + let form: XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor + form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow + + var section: XLFormSectionDescriptor + var row: XLFormRowDescriptor + + // Section: Destination Folder + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("_save_path_", comment: "").uppercased()) + section.footerTitle = " " + form.addFormSection(section) + + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFolderCustomCellType"] = FolderPathCustomCell.self + row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "kNMCFolderCustomCellType", title: "") + row.action.formSelector = #selector(changeDestinationFolder(_:)) + row.cellConfig["folderImage.image"] = UIImage(named: "folder")!.imageColor(NCBrandColor.shared.customer) + row.cellConfig["photoLabel.textAlignment"] = NSTextAlignment.right.rawValue + row.cellConfig["photoLabel.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["photoLabel.textColor"] = UIColor.label //photos + if(self.fileNameFolder == "/"){ + row.cellConfig["photoLabel.text"] = NSLocalizedString("_prefix_upload_path_", comment: "") + }else{ + row.cellConfig["photoLabel.text"] = self.fileNameFolder + } + row.cellConfig["textLabel.text"] = "" + + section.addFormRow(row) + + // Section: File Name + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("_filename_", comment: "").uppercased()) + form.addFormSection(section) + + XLFormViewController.cellClassesForRowDescriptorTypes()["kMyAppCustomCellType"] = NCCreateDocumentCustomTextField.self + + row = XLFormRowDescriptor(tag: "fileName", rowType: "kMyAppCustomCellType", title: NSLocalizedString("_filename_", comment: "")) + row.cellClass = NCCreateDocumentCustomTextField.self + + row.cellConfigAtConfigure["backgroundColor"] = UIColor.secondarySystemGroupedBackground; + row.cellConfig["fileNameTextField.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["fileNameTextField.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["fileNameTextField.textColor"] = UIColor.label + row.cellConfig["fileNameTextField.placeholder"] = self.fileName + + section.addFormRow(row) + + self.form = form + // tableView.reloadData() + // collectionView.reloadData() + } + + override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + let header = view as? UITableViewHeaderFooterView + header?.textLabel?.font = UIFont.systemFont(ofSize: 13.0) + header?.textLabel?.textColor = .gray + header?.tintColor = tableView.backgroundColor + } + + override func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { + let header = view as? UITableViewHeaderFooterView + header?.textLabel?.font = UIFont.systemFont(ofSize: 13.0) + header?.textLabel?.textColor = .gray + header?.tintColor = tableView.backgroundColor + } + + // MARK: - CollectionView + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return listOfTemplate.count + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + + let itemWidth: CGFloat = (collectionView.frame.width - (sectionInsets * 4) - CGFloat(numItems)) / CGFloat(numItems) + let itemHeight: CGFloat = itemWidth + highLabelName + + collectionViewHeigth.constant = itemHeight + sectionInsets + + return CGSize(width: itemWidth, height: itemHeight) + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) + + let template = listOfTemplate[indexPath.row] + + // image + let imagePreview = cell.viewWithTag(100) as? UIImageView + if !template.preview.isEmpty { + let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + template.name + ".png" + if FileManager.default.fileExists(atPath: fileNameLocalPath) { + let imageURL = URL(fileURLWithPath: fileNameLocalPath) + if let image = UIImage(contentsOfFile: imageURL.path) { + imagePreview?.image = image + } + } else { + getImageFromTemplate(name: template.name, preview: template.preview, indexPath: indexPath) + } + } + + // name + let name = cell.viewWithTag(200) as? UILabel + name?.text = template.name + name?.textColor = .secondarySystemGroupedBackground + + // select + let imageSelect = cell.viewWithTag(300) as? UIImageView + if selectTemplate != nil && selectTemplate?.name == template.name { + cell.backgroundColor = .label + imageSelect?.image = UIImage(named: "plus100") + imageSelect?.isHidden = false + } else { + cell.backgroundColor = .secondarySystemGroupedBackground + imageSelect?.isHidden = true + } + + return cell + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + + let template = listOfTemplate[indexPath.row] + + selectTemplate = template + fileNameExtension = template.ext + + collectionView.reloadData() + } + + // MARK: - Action + + func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], overwrite: Bool, copy: Bool, move: Bool) { + + guard let serverUrl = serverUrl else { return } + + self.serverUrl = serverUrl + if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) { + fileNameFolder = "/" + } else { + fileNameFolder = (serverUrl as NSString).lastPathComponent + } + + let buttonDestinationFolder: XLFormRowDescriptor = self.form.formRow(withTag: "ButtonDestinationFolder")! + buttonDestinationFolder.cellConfig["photoLabel.text"] = fileNameFolder + + self.tableView.reloadData() + } + +// override func formRowDescriptorValueHasChanged(_ formRow: XLFormRowDescriptor!, oldValue: Any!, newValue: Any!) { +// super.formRowDescriptorValueHasChanged(formRow, oldValue: oldValue, newValue: newValue) +//// if formRow.tag == "fileName" { +//// self.form.delegate = nil +//// if let fileNameNew = formRow.value { +//// self.fileName = CCUtility.removeForbiddenCharactersServer(fileNameNew as? String) +//// } +//// formRow.value = self.fileName +//// self.form.delegate = self +//// } +// } + + @objc func changeDestinationFolder(_ sender: XLFormRowDescriptor) { + + self.deselectFormRow(sender) + + 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 = .selectCreateFolder + + self.present(navigationController, animated: true, completion: nil) + } + } + + @objc func save() { + + guard let selectTemplate = self.selectTemplate else { return } + templateIdentifier = selectTemplate.identifier + + let rowFileName: XLFormRowDescriptor = self.form.formRow(withTag: "fileName")! + var fileName = rowFileName.value as? String + if fileName?.isEmpty ?? false || fileName == nil { + fileName = NCUtilityFileSystem().createFileNameDate("Text", ext: getFileExtension()) + } else if fileName?.trimmingCharacters(in: .whitespaces).isEmpty ?? false { + let alert = UIAlertController(title: "", message: NSLocalizedString("_please_enter_file_name_", comment: ""), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .cancel, handler: nil)) + self.present(alert, animated: true) + return + } + guard var fileNameForm: String = fileName, !fileNameForm.isEmpty else { return } + + // Trim whitespaces after checks above + fileNameForm = fileNameForm.trimmingCharacters(in: .whitespacesAndNewlines) + + let result = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameForm, mimeType: "", directory: false) + if utility.isDirectEditing(account: appDelegate.account, contentType: result.mimeType).isEmpty { + fileNameForm = (fileNameForm as NSString).deletingPathExtension + "." + fileNameExtension + } + + if NCManageDatabase.shared.getMetadataConflict(account: appDelegate.account, serverUrl: serverUrl, fileNameView: String(describing: fileNameForm)) != nil { + + let metadataForUpload = NCManageDatabase.shared.createMetadata(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId, fileName: String(describing: fileNameForm), fileNameView: String(describing: fileNameForm), ocId: "", serverUrl: serverUrl, urlBase: appDelegate.urlBase, url: "", contentType: "") + + guard let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict else { return } + + conflict.textLabelDetailNewFile = NSLocalizedString("_now_", comment: "") + conflict.alwaysNewFileNameNumber = true + conflict.serverUrl = serverUrl + conflict.metadatasUploadInConflict = [metadataForUpload] + conflict.delegate = self + + self.present(conflict, animated: true, completion: nil) + + } else { + + let fileNamePath = utilityFileSystem.getFileNamePath(String(describing: fileNameForm), serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId) + createDocument(fileNamePath: fileNamePath, fileName: String(describing: fileNameForm)) + } + } + + func dismissCreateFormUploadConflict(metadatas: [tableMetadata]?) { + + if let metadatas, metadatas.count > 0 { + let fileName = metadatas[0].fileName + let fileNamePath = utilityFileSystem.getFileNamePath(fileName, serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId) + createDocument(fileNamePath: fileNamePath, fileName: fileName) + } else { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + self.cancel() + } + } + } + + func createDocument(fileNamePath: String, fileName: String) { + + self.navigationItem.rightBarButtonItem?.isEnabled = false + var UUID = NSUUID().uuidString + UUID = "TEMP" + UUID.replacingOccurrences(of: "-", with: "") + + if self.editorId == NCGlobal.shared.editorText || self.editorId == NCGlobal.shared.editorOnlyoffice { + + var options = NKRequestOptions() + if self.editorId == NCGlobal.shared.editorOnlyoffice { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentOnlyOffice()) + } else if editorId == NCGlobal.shared.editorText { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) + } + + NextcloudKit.shared.NCTextCreateFile(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, options: options) { account, url, _, error in + guard error == .success, account == self.appDelegate.account, let url = url else { + self.navigationItem.rightBarButtonItem?.isEnabled = true + NCContentPresenter().showError(error: error) + return + } + + var results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileName, mimeType: "", directory: false) + // FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown + if results.mimeType.isEmpty { + results.mimeType = "text/x-markdown" + } + + self.dismiss(animated: true, completion: { + let metadata = NCManageDatabase.shared.createMetadata(account: self.appDelegate.account, user: self.appDelegate.user, userId: self.appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: UUID, serverUrl: self.serverUrl, urlBase: self.appDelegate.urlBase, url: url, contentType: results.mimeType) + if let viewController = self.appDelegate.activeViewController { + NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil) + } + }) + } + } + + if self.editorId == NCGlobal.shared.editorCollabora { + + NextcloudKit.shared.createRichdocuments(path: fileNamePath, templateId: templateIdentifier) { account, url, _, error in + guard error == .success, account == self.appDelegate.account, let url = url else { + self.navigationItem.rightBarButtonItem?.isEnabled = true + NCContentPresenter().showError(error: error) + return + } + + self.dismiss(animated: true, completion: { + let createFileName = (fileName as NSString).deletingPathExtension + "." + self.fileNameExtension + let metadata = NCManageDatabase.shared.createMetadata(account: self.appDelegate.account, user: self.appDelegate.user, userId: self.appDelegate.userId, fileName: createFileName, fileNameView: createFileName, ocId: UUID, serverUrl: self.serverUrl, urlBase: self.appDelegate.urlBase, url: url, contentType: "") + if let viewController = self.appDelegate.activeViewController { + NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil) + } + }) + } + } + } + + @objc func cancel() { + + self.dismiss(animated: true, completion: nil) + } + + // MARK: NC API + + func getTemplate() { + + indicator.color = NCBrandColor.shared.brandElement + indicator.startAnimating() + + if self.editorId == NCGlobal.shared.editorText || self.editorId == NCGlobal.shared.editorOnlyoffice { + + var options = NKRequestOptions() + if self.editorId == NCGlobal.shared.editorOnlyoffice { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentOnlyOffice()) + } else if editorId == NCGlobal.shared.editorText { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) + } + + NextcloudKit.shared.NCTextGetListOfTemplates(options: options) { account, templates, _, error in + + self.indicator.stopAnimating() + + if error == .success && account == self.appDelegate.account { + + for template in templates { + + let temp = NKEditorTemplates() + + temp.identifier = template.identifier + temp.ext = template.ext + temp.name = template.name + temp.preview = template.preview + + self.listOfTemplate.append(temp) + + // default: template empty + if temp.preview.isEmpty { + self.selectTemplate = temp + self.fileNameExtension = template.ext + self.navigationItem.rightBarButtonItem?.isEnabled = true + } + } + } + + if self.listOfTemplate.isEmpty { + + let temp = NKEditorTemplates() + + temp.identifier = "" + if self.editorId == NCGlobal.shared.editorText { + temp.ext = "md" + } else if self.editorId == NCGlobal.shared.editorOnlyoffice && self.typeTemplate == NCGlobal.shared.templateDocument { + temp.ext = "docx" + } else if self.editorId == NCGlobal.shared.editorOnlyoffice && self.typeTemplate == NCGlobal.shared.templateSpreadsheet { + temp.ext = "xlsx" + } else if self.editorId == NCGlobal.shared.editorOnlyoffice && self.typeTemplate == NCGlobal.shared.templatePresentation { + temp.ext = "pptx" + } + temp.name = "Empty" + temp.preview = "" + + self.listOfTemplate.append(temp) + + self.selectTemplate = temp + self.fileNameExtension = temp.ext + self.navigationItem.rightBarButtonItem?.isEnabled = true + } + + self.collectionView.reloadData() + } + + } + + if self.editorId == NCGlobal.shared.editorCollabora { + + NextcloudKit.shared.getTemplatesRichdocuments(typeTemplate: typeTemplate) { account, templates, _, error in + + self.indicator.stopAnimating() + + if error == .success && account == self.appDelegate.account { + + for template in templates! { + + let temp = NKEditorTemplates() + + temp.identifier = "\(template.templateId)" + temp.delete = template.delete + temp.ext = template.ext + temp.name = template.name + temp.preview = template.preview + temp.type = template.type + + self.listOfTemplate.append(temp) + + // default: template empty + if temp.preview.isEmpty { + self.selectTemplate = temp + self.fileNameExtension = temp.ext + self.navigationItem.rightBarButtonItem?.isEnabled = true + } + } + } + + if self.listOfTemplate.isEmpty { + + let temp = NKEditorTemplates() + + temp.identifier = "" + if self.typeTemplate == NCGlobal.shared.templateDocument { + temp.ext = "docx" + } else if self.typeTemplate == NCGlobal.shared.templateSpreadsheet { + temp.ext = "xlsx" + } else if self.typeTemplate == NCGlobal.shared.templatePresentation { + temp.ext = "pptx" + } + temp.name = "Empty" + temp.preview = "" + + self.listOfTemplate.append(temp) + + self.selectTemplate = temp + self.fileNameExtension = temp.ext + self.navigationItem.rightBarButtonItem?.isEnabled = true + } + + self.collectionView.reloadData() + } + } + } + + func getImageFromTemplate(name: String, preview: String, indexPath: IndexPath) { + + let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + name + ".png" + + NextcloudKit.shared.download(serverUrlFileName: preview, fileNameLocalPath: fileNameLocalPath, requestHandler: { _ in + + }, taskHandler: { _ in + + }, progressHandler: { _ in + + }) { account, _, _, _, _, _, error in + + if error == .success && account == self.appDelegate.account { + self.collectionView.reloadItems(at: [indexPath]) + } else if error != .success { + print("\(error.errorCode)") + } else { + print("[ERROR] It has been changed user during networking process, error.") + } + } + } + + func getFileExtension() -> String { + switch typeTemplate { + case NCGlobal.shared.editorText: + return "md" + case NCGlobal.shared.templateDocument: + return "docx" + case NCGlobal.shared.templateSpreadsheet: + return "xlsx" + case NCGlobal.shared.templatePresentation: + return "pptx" + default: + return "" + } + } +} diff --git a/iOSClient/NMC Custom Views/FolderPathCustomCell.swift b/iOSClient/NMC Custom Views/FolderPathCustomCell.swift new file mode 100644 index 0000000000..901ce667ae --- /dev/null +++ b/iOSClient/NMC Custom Views/FolderPathCustomCell.swift @@ -0,0 +1,33 @@ +// +// FolderPathCustomCell.swift +// Nextcloud +// +// Created by A200073704 on 04/05/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit + +class FolderPathCustomCell: XLFormButtonCell{ + + @IBOutlet weak var photoLabel: UILabel! + @IBOutlet weak var folderImage: UIImageView! + @IBOutlet weak var bottomLineView: UIView! + + override func awakeFromNib() { + super.awakeFromNib() + } + + override func configure() { + super.configure() + } + + override func update() { + super.update() + if (rowDescriptor.tag == "PhotoButtonDestinationFolder"){ + bottomLineView.isHidden = true + }else{ + bottomLineView.isHidden = false + } + } +} diff --git a/iOSClient/NMC Custom Views/FolderPathCustomCell.xib b/iOSClient/NMC Custom Views/FolderPathCustomCell.xib new file mode 100644 index 0000000000..a231ae7c72 --- /dev/null +++ b/iOSClient/NMC Custom Views/FolderPathCustomCell.xib @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift new file mode 100644 index 0000000000..0242fa0f7a --- /dev/null +++ b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift @@ -0,0 +1,70 @@ +// +// NCCreateDocumentCustomTextField.swift +// Nextcloud +// +// Created by A200073704 on 04/05/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit + +class NCCreateDocumentCustomTextField: XLFormBaseCell,UITextFieldDelegate { + + @IBOutlet weak var fileNameTextField: UITextField! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + + fileNameTextField.delegate = self + + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + + override func configure() { + super.configure() + } + + override func update() { + super.update() + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + if fileNameTextField == textField { + if let rowDescriptor = rowDescriptor { + if let text = textField.text{ + if (text + " ").isEmpty == false { + rowDescriptor.value = self.fileNameTextField.text! + string + } else { + rowDescriptor.value = nil + } + } + } + } + + self.formViewController().textField(textField, shouldChangeCharactersIn: range, replacementString: string) + + + return true + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + self.formViewController()?.textFieldShouldReturn(fileNameTextField) + return true + } + + func textFieldShouldClear(_ textField: UITextField) -> Bool { + self.formViewController()?.textFieldShouldClear(fileNameTextField) + return true + } + + override class func formDescriptorCellHeight(for rowDescriptor: XLFormRowDescriptor!) -> CGFloat { + return 45 + } +} diff --git a/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.xib b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.xib new file mode 100644 index 0000000000..3fd86bb2d6 --- /dev/null +++ b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.xib @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From fa75404fb9d94bb694d47969c25bf4beccf4910c Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Fri, 11 Apr 2025 10:54:41 +0530 Subject: [PATCH 007/177] NMC 1933 - Collabora customisations changes --- Nextcloud.xcodeproj/project.pbxproj | 39 +++++ .../Data/NCManageDatabase+Metadata.swift | 144 +++++++++++++++++- .../NCCreateFormUploadDocuments.swift | 55 ++++--- 3 files changed, 207 insertions(+), 31 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 0d081f4036..9fd4fadc94 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -91,6 +91,15 @@ AFCE353527E4ED5900FEA6C2 /* DateFormatter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */; }; AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; }; AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* Shareable.swift */; }; + AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */; }; + B52FAE902DA8D9E1001AB1BD /* CollaboraTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FAE8F2DA8D9E1001AB1BD /* CollaboraTestCase.swift */; }; + B52FAE932DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B52FAE912DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard */; }; + B52FAE942DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FAE922DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift */; }; + B52FAE9C2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = B52FAE9A2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib */; }; + B52FAE9D2DA8DED9001AB1BD /* FolderPathCustomCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B52FAE982DA8DED9001AB1BD /* FolderPathCustomCell.xib */; }; + B52FAE9E2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FAE992DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift */; }; + B52FAE9F2DA8DED9001AB1BD /* FolderPathCustomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FAE972DA8DED9001AB1BD /* FolderPathCustomCell.swift */; }; + C04E2F232A17BB4D001BAD85 /* FilesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */; }; D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; F310B1EF2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F310B1EE2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift */; }; @@ -1367,6 +1376,14 @@ AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extension.swift"; sourceTree = ""; }; AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareCells.swift; sourceTree = ""; }; AFCE353827E5DE0400FEA6C2 /* Shareable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shareable.swift; sourceTree = ""; }; + AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShare+Helper.swift"; sourceTree = ""; }; + B52FAE8F2DA8D9E1001AB1BD /* CollaboraTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollaboraTestCase.swift; sourceTree = ""; }; + B52FAE912DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCCreateFormUploadDocuments.storyboard; sourceTree = ""; }; + B52FAE922DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCreateFormUploadDocuments.swift; sourceTree = ""; }; + B52FAE972DA8DED9001AB1BD /* FolderPathCustomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderPathCustomCell.swift; sourceTree = ""; }; + B52FAE982DA8DED9001AB1BD /* FolderPathCustomCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FolderPathCustomCell.xib; sourceTree = ""; }; + B52FAE992DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCreateDocumentCustomTextField.swift; sourceTree = ""; }; + B52FAE9A2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCCreateDocumentCustomTextField.xib; sourceTree = ""; }; C0046CDA2A17B98400D87C9D /* NextcloudUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; @@ -2171,6 +2188,8 @@ isa = PBXGroup; children = ( AA52EB452D42AC5A0089C348 /* Placeholder.swift */, + B52FAE8F2DA8D9E1001AB1BD /* CollaboraTestCase.swift */, + AF8ED1FB2757821000B8DBC4 /* NextcloudUnitTests.swift */, ); path = NextcloudUnitTests; sourceTree = ""; @@ -2209,6 +2228,17 @@ path = Advanced; sourceTree = ""; }; + B52FAE9B2DA8DED9001AB1BD /* NMC Custom Views */ = { + isa = PBXGroup; + children = ( + B52FAE972DA8DED9001AB1BD /* FolderPathCustomCell.swift */, + B52FAE982DA8DED9001AB1BD /* FolderPathCustomCell.xib */, + B52FAE992DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift */, + B52FAE9A2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib */, + ); + path = "NMC Custom Views"; + sourceTree = ""; + }; C0046CDB2A17B98400D87C9D /* NextcloudUITests */ = { isa = PBXGroup; children = ( @@ -3229,6 +3259,8 @@ F7DFB7E9219C5A0500680748 /* Create cloud */ = { isa = PBXGroup; children = ( + B52FAE912DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard */, + B52FAE922DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift */, F7FA7FFD2C0F4F3B0072FC60 /* Upload Assets */, F7A509242C26BD5D00326106 /* NCCreateDocument.swift */, F704B5E22430AA6F00632F5F /* NCCreateFormUploadConflict.storyboard */, @@ -3354,6 +3386,7 @@ isa = PBXGroup; children = ( AA517BB42D66149900F8D37C /* .tx */, + B52FAE9B2DA8DED9001AB1BD /* NMC Custom Views */, F702F2CC25EE5B4F008F8E80 /* AppDelegate.swift */, F794E13E2BBC0F70003693D7 /* SceneDelegate.swift */, F7CF067A2E0FF38F0063AD04 /* NCAppStateManager.swift */, @@ -4086,6 +4119,8 @@ F717402D24F699A5000C87D5 /* NCFavorite.storyboard in Resources */, F723B3DD22FC6D1D00301EFE /* NCShareCommentsCell.xib in Resources */, F78ACD4B21903F850088454D /* NCTrashListCell.xib in Resources */, + B52FAE9C2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib in Resources */, + B52FAE9D2DA8DED9001AB1BD /* FolderPathCustomCell.xib in Resources */, AF93471927E2361E002537EE /* NCShareAdvancePermissionFooter.xib in Resources */, F7725A61251F33BB00D125E0 /* NCFiles.storyboard in Resources */, F7CBC1232BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */, @@ -4105,6 +4140,7 @@ F78ACD54219047D40088454D /* NCSectionFooter.xib in Resources */, F751247E2C42919C00E63DB8 /* NCPhotoCell.xib in Resources */, F704B5E32430AA6F00632F5F /* NCCreateFormUploadConflict.storyboard in Resources */, + B52FAE932DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard in Resources */, F7EDE509262DA9D600414FE6 /* NCSelectCommandViewSelect.xib in Resources */, F732D23327CF8AED000B0F1B /* NCPlayerToolBar.xib in Resources */, F73D11FA253C5F4800DF9BEC /* NCViewerNextcloudText.storyboard in Resources */, @@ -4657,6 +4693,7 @@ F7E7AEA72BA32D0000512E52 /* NCCollectionViewUnifiedSearch.swift in Sources */, F73EF7A72B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */, F33918C42C7CD8F2002D9AA1 /* FileNameValidator+Extensions.swift in Sources */, + B52FAE942DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift in Sources */, F39170AD2CB82024006127BC /* FileAutoRenamer+Extensions.swift in Sources */, F799DF882C4B83CC003410B5 /* NCCollectionViewCommon+EasyTipView.swift in Sources */, F7AE00F8230E81CB007ACF8A /* NCBrowserWeb.swift in Sources */, @@ -4788,6 +4825,8 @@ F3A047972BD2668800658E7B /* NCAssistantEmptyView.swift in Sources */, F757CC8D29E82D0500F31428 /* NCGroupfolders.swift in Sources */, F760329F252F0F8E0015A421 /* NCTransferCell.swift in Sources */, + B52FAE9E2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift in Sources */, + B52FAE9F2DA8DED9001AB1BD /* FolderPathCustomCell.swift in Sources */, F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */, AF68326A27BE65A90010BF0B /* NCMenuAction.swift in Sources */, F7F3E58E2D3BB65600A32B14 /* NCNetworking+Recommendations.swift in Sources */, diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index c85e133fe9..8e6add4e24 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -152,11 +152,25 @@ extension tableMetadata { } return true } + + var isPrintable: Bool { + if isDocumentViewableOnly { + return false + } + if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKCommon.TypeClassFile.image.rawValue { + return true + } + return false + } var isSavebleInCameraRoll: Bool { return (classFile == NKTypeClassFile.image.rawValue && contentType != "image/svg+xml") || classFile == NKTypeClassFile.video.rawValue } + var isDocumentViewableOnly: Bool { + sharePermissionsCollaborationServices == NCPermissions().permissionReadShare && classFile == NKCommon.TypeClassFile.document.rawValue + } + var isAudioOrVideo: Bool { return classFile == NKTypeClassFile.audio.rawValue || classFile == NKTypeClassFile.video.rawValue } @@ -182,15 +196,15 @@ extension tableMetadata { } var isCopyableInPasteboard: Bool { - !directory + !isDocumentViewableOnly && !directory } var isCopyableMovable: Bool { - !isDirectoryE2EE && !e2eEncrypted + !isDocumentViewableOnly && !isDirectoryE2EE && !e2eEncrypted } var isModifiableWithQuickLook: Bool { - if directory || isDirectoryE2EE { + if directory || isDocumentViewableOnly || isDirectoryE2EE { return false } return isPDF || isImage @@ -212,17 +226,43 @@ extension tableMetadata { } var canSetAsAvailableOffline: Bool { - return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted + return session.isEmpty && !isDocumentViewableOnly //!isDirectoryE2EE && !e2eEncrypted } var canShare: Bool { - return session.isEmpty && !directory && !NCBrandOptions.shared.disable_openin_file + return session.isEmpty && !isDocumentViewableOnly && !directory && !NCBrandOptions.shared.disable_openin_file } var canUnsetDirectoryAsE2EE: Bool { return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) } + var canOpenExternalEditor: Bool { + if isDocumentViewableOnly { + return false + } + let utility = NCUtility() + let editors = utility.editorsDirectEditing(account: account, contentType: contentType) + let isRichDocument = utility.isTypeFileRichDocument(self) + return classFile == NKCommon.TypeClassFile.document.rawValue && editors.contains(NCGlobal.shared.editorText) && ((editors.contains(NCGlobal.shared.editorOnlyoffice) || isRichDocument)) + } + + var isWaitingTransfer: Bool { + status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusWaitUpload || status == NCGlobal.shared.metadataStatusUploadError + } + + var isInTransfer: Bool { + status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading + } + + var isTransferInForeground: Bool { + (status > 0 && (chunk > 0 || e2eEncrypted)) + } + + var isDownloadUpload: Bool { + status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading + } + var isDownload: Bool { status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusDownloading } @@ -321,7 +361,7 @@ extension tableMetadata { // if !NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingApiEnabled || (NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEEnabled && isDirectoryE2EE), !e2eEncrypted { return false } - return true + return !e2eEncrypted } /// Returns a detached (unmanaged) deep copy of the current `tableMetadata` object. @@ -485,6 +525,42 @@ extension NCManageDatabase { } completion(tableMetadata(value: metadataFolder), metadatas) } + + func convertFilesToMetadatas(_ files: [NKFile], useMetadataFolder: Bool, completion: @escaping (_ metadataFolder: tableMetadata, _ metadatasFolder: [tableMetadata], _ metadatas: [tableMetadata]) -> Void) { + + var counter: Int = 0 + var isDirectoryE2EE: Bool = false + let listServerUrl = ThreadSafeDictionary() + + var metadataFolder = tableMetadata() + var metadataFolders: [tableMetadata] = [] + var metadatas: [tableMetadata] = [] + + for file in files { + + if let key = listServerUrl[file.serverUrl] { + isDirectoryE2EE = key + } else { + isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file) + listServerUrl[file.serverUrl] = isDirectoryE2EE + } + + let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) + + if counter == 0 && useMetadataFolder { + metadataFolder = tableMetadata.init(value: metadata) + } else { + metadatas.append(metadata) + if metadata.directory { + metadataFolders.append(metadata) + } + } + + counter += 1 + } + + completion(metadataFolder, metadataFolders, metadatas) + } func convertFilesToMetadatasAsync(_ files: [NKFile], useFirstAsMetadataFolder: Bool) async -> (metadataFolder: tableMetadata, metadatas: [tableMetadata]) { await withCheckedContinuation { continuation in @@ -1397,7 +1473,7 @@ extension NCManageDatabase { counter += 1 listIdentifierRank[item.ocId] = NSNumber(value: counter) } - + return listIdentifierRank } return result ?? [:] @@ -1410,6 +1486,18 @@ extension NCManageDatabase { } } + @objc func clearMetadatasUpload(account: String) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter("account == %@ AND (status == %d OR status == %d)", account, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploadError) + realm.delete(results) + } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") + } + } + func getMetadataFromFileId(_ fileId: String?) -> tableMetadata? { guard let fileId else { return nil @@ -1634,6 +1722,19 @@ extension NCManageDatabase { } } + func getMediaMetadatas(predicate: NSPredicate) -> ThreadSafeArray? { + + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: "date", ascending: false) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") + } + + return nil + } + func getMediaMetadatas(predicate: NSPredicate, sorted: String? = nil, ascending: Bool = false) -> ThreadSafeArray? { do { @@ -1661,4 +1762,33 @@ extension NCManageDatabase { } return nil } + + func getAdvancedMetadatas(predicate: NSPredicate, page: Int = 0, limit: Int = 0, sorted: String, ascending: Bool) -> [tableMetadata] { + + var metadatas: [tableMetadata] = [] + + do { + let realm = try Realm() + realm.refresh() + let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending) + if !results.isEmpty { + if page == 0 || limit == 0 { + return Array(results.map { tableMetadata.init(value: $0) }) + } else { + let nFrom = (page - 1) * limit + let nTo = nFrom + (limit - 1) + for n in nFrom...nTo { + if n == results.count { + break + } + metadatas.append(tableMetadata.init(value: results[n])) + } + } + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") + } + + return metadatas + } } diff --git a/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift b/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift index 61177fa174..ec489d893a 100644 --- a/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift +++ b/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift @@ -53,15 +53,20 @@ import XLForm let sectionInsets: CGFloat = 10 let highLabelName: CGFloat = 20 + var controller: NCMainTabBarController! + var session: NCSession.Session { + NCSession.shared.getSession(controller: controller) + } + // MARK: - View Life Cycle override func viewDidLoad() { super.viewDidLoad() - if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) { + if serverUrl == utilityFileSystem.getHomeServer(session: session) { fileNameFolder = "/" } else { - fileNameFolder = (serverUrl as NSString).lastPathComponent + fileNameFolder = utilityFileSystem.getTextServerUrl(session: session, serverUrl: serverUrl)//(serverUrl as NSString).lastPathComponent } self.tableView.separatorStyle = UITableViewCell.SeparatorStyle.none @@ -229,7 +234,7 @@ import XLForm guard let serverUrl = serverUrl else { return } self.serverUrl = serverUrl - if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) { + if serverUrl == utilityFileSystem.getHomeServer(session: session) { fileNameFolder = "/" } else { fileNameFolder = (serverUrl as NSString).lastPathComponent @@ -288,14 +293,15 @@ import XLForm // Trim whitespaces after checks above fileNameForm = fileNameForm.trimmingCharacters(in: .whitespacesAndNewlines) - let result = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameForm, mimeType: "", directory: false) - if utility.isDirectEditing(account: appDelegate.account, contentType: result.mimeType).isEmpty { + let result = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameForm, mimeType: "", directory: false, account: session.account + ) + if utility.editorsDirectEditing(account: session.account, contentType: result.mimeType).isEmpty { fileNameForm = (fileNameForm as NSString).deletingPathExtension + "." + fileNameExtension } - if NCManageDatabase.shared.getMetadataConflict(account: appDelegate.account, serverUrl: serverUrl, fileNameView: String(describing: fileNameForm)) != nil { + if NCManageDatabase.shared.getMetadataConflict(account: session.account, serverUrl: serverUrl, fileNameView: String(describing: fileNameForm), nativeFormat: false) != nil { - let metadataForUpload = NCManageDatabase.shared.createMetadata(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId, fileName: String(describing: fileNameForm), fileNameView: String(describing: fileNameForm), ocId: "", serverUrl: serverUrl, urlBase: appDelegate.urlBase, url: "", contentType: "") + let metadataForUpload = NCManageDatabase.shared.createMetadata(fileName: String(describing: fileNameForm), fileNameView: String(describing: fileNameForm), ocId: UUID().uuidString, serverUrl: serverUrl, url: "", contentType: "", session: session, sceneIdentifier: self.appDelegate.sceneIdentifier) guard let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict else { return } @@ -309,7 +315,7 @@ import XLForm } else { - let fileNamePath = utilityFileSystem.getFileNamePath(String(describing: fileNameForm), serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId) + let fileNamePath = utilityFileSystem.getFileNamePath(String(describing: fileNameForm), serverUrl: serverUrl, session: session) createDocument(fileNamePath: fileNamePath, fileName: String(describing: fileNameForm)) } } @@ -318,7 +324,7 @@ import XLForm if let metadatas, metadatas.count > 0 { let fileName = metadatas[0].fileName - let fileNamePath = utilityFileSystem.getFileNamePath(fileName, serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId) + let fileNamePath = utilityFileSystem.getFileNamePath(fileName, serverUrl: serverUrl, session: session) createDocument(fileNamePath: fileNamePath, fileName: fileName) } else { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { @@ -342,23 +348,23 @@ import XLForm options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) } - NextcloudKit.shared.NCTextCreateFile(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, options: options) { account, url, _, error in - guard error == .success, account == self.appDelegate.account, let url = url else { + NextcloudKit.shared.NCTextCreateFile(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, account: session.account, options: options) { account, url, _, error in + guard error == .success, account == self.session.account, let url = url else { self.navigationItem.rightBarButtonItem?.isEnabled = true NCContentPresenter().showError(error: error) return } - var results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileName, mimeType: "", directory: false) + var results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileName, mimeType: "", directory: false, account: self.session.account) // FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown if results.mimeType.isEmpty { results.mimeType = "text/x-markdown" } self.dismiss(animated: true, completion: { - let metadata = NCManageDatabase.shared.createMetadata(account: self.appDelegate.account, user: self.appDelegate.user, userId: self.appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: UUID, serverUrl: self.serverUrl, urlBase: self.appDelegate.urlBase, url: url, contentType: results.mimeType) + let metadata = NCManageDatabase.shared.createMetadata(fileName: fileName, fileNameView: fileName, ocId: UUID, serverUrl: self.serverUrl, url: url, contentType: results.mimeType, session: self.session, sceneIdentifier: self.appDelegate.sceneIdentifier) if let viewController = self.appDelegate.activeViewController { - NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil) + NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata]) } }) } @@ -366,8 +372,8 @@ import XLForm if self.editorId == NCGlobal.shared.editorCollabora { - NextcloudKit.shared.createRichdocuments(path: fileNamePath, templateId: templateIdentifier) { account, url, _, error in - guard error == .success, account == self.appDelegate.account, let url = url else { + NextcloudKit.shared.createRichdocuments(path: fileNamePath, templateId: templateIdentifier, account: session.account) { account, url, _, error in + guard error == .success, account == self.session.account, let url = url else { self.navigationItem.rightBarButtonItem?.isEnabled = true NCContentPresenter().showError(error: error) return @@ -375,9 +381,10 @@ import XLForm self.dismiss(animated: true, completion: { let createFileName = (fileName as NSString).deletingPathExtension + "." + self.fileNameExtension - let metadata = NCManageDatabase.shared.createMetadata(account: self.appDelegate.account, user: self.appDelegate.user, userId: self.appDelegate.userId, fileName: createFileName, fileNameView: createFileName, ocId: UUID, serverUrl: self.serverUrl, urlBase: self.appDelegate.urlBase, url: url, contentType: "") + let metadata = NCManageDatabase.shared.createMetadata(fileName: createFileName, fileNameView: createFileName, ocId: UUID, serverUrl: self.serverUrl, url: url, contentType: "", session: self.session, sceneIdentifier: self.appDelegate.sceneIdentifier) + AnalyticsHelper.shared.trackCreateFile(metadata: metadata) if let viewController = self.appDelegate.activeViewController { - NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil) + NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata]) } }) } @@ -405,11 +412,11 @@ import XLForm options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) } - NextcloudKit.shared.NCTextGetListOfTemplates(options: options) { account, templates, _, error in + NextcloudKit.shared.NCTextGetListOfTemplates(account: session.account, options: options) { account, templates, _, error in self.indicator.stopAnimating() - if error == .success && account == self.appDelegate.account { + if error == .success && account == self.session.account, let templates = templates { for template in templates { @@ -462,11 +469,11 @@ import XLForm if self.editorId == NCGlobal.shared.editorCollabora { - NextcloudKit.shared.getTemplatesRichdocuments(typeTemplate: typeTemplate) { account, templates, _, error in + NextcloudKit.shared.getTemplatesRichdocuments(typeTemplate: typeTemplate, account: session.account) { account, templates, _, error in self.indicator.stopAnimating() - if error == .success && account == self.appDelegate.account { + if error == .success && account == self.session.account { for template in templates! { @@ -521,7 +528,7 @@ import XLForm let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + name + ".png" - NextcloudKit.shared.download(serverUrlFileName: preview, fileNameLocalPath: fileNameLocalPath, requestHandler: { _ in + NextcloudKit.shared.download(serverUrlFileName: preview, fileNameLocalPath: fileNameLocalPath, account: session.account, requestHandler: { _ in }, taskHandler: { _ in @@ -529,7 +536,7 @@ import XLForm }) { account, _, _, _, _, _, error in - if error == .success && account == self.appDelegate.account { + if error == .success && account == self.session.account { self.collectionView.reloadItems(at: [indexPath]) } else if error != .success { print("\(error.errorCode)") From f32b5da2f238ed59f9c96515451140f06a55bef6 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Wed, 1 Oct 2025 14:13:06 +0530 Subject: [PATCH 008/177] NMC 1933 - Collabora customisation changes --- iOSClient/BrowserWeb/NCBrowserWeb.swift | 25 +- .../Data/NCManageDatabase+Metadata.swift | 1472 +++++++---------- .../NCCreateFormUploadDocuments.swift | 48 +- .../FolderPathCustomCell.swift | 1 + .../NMC Custom Views/FolderPathCustomCell.xib | 11 +- .../NCCreateDocumentCustomTextField.swift | 1 + 6 files changed, 702 insertions(+), 856 deletions(-) diff --git a/iOSClient/BrowserWeb/NCBrowserWeb.swift b/iOSClient/BrowserWeb/NCBrowserWeb.swift index 4bf4a4f613..10ee6d71ca 100644 --- a/iOSClient/BrowserWeb/NCBrowserWeb.swift +++ b/iOSClient/BrowserWeb/NCBrowserWeb.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2019 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCBrowserWeb.swift +// Nextcloud +// +// Created by Marino Faggiana on 22/08/2019. +// Copyright (c) 2019 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// import UIKit @preconcurrency import WebKit diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index 8e6add4e24..5c82a8767a 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -1,12 +1,30 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2021 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCManageDatabase+Metadata.swift +// Nextcloud +// +// Created by Henrik Storch on 30.11.21. +// Copyright © 2021 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// import Foundation import UIKit import RealmSwift import NextcloudKit -import Photos class tableMetadata: Object { override func isEqual(_ object: Any?) -> Bool { @@ -33,7 +51,8 @@ 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.shareType).elementsEqual(Array(object.shareType)), + Array(self.sharePermissionsCloudMesh).elementsEqual(Array(object.sharePermissionsCloudMesh)) { return true } else { return false @@ -56,6 +75,7 @@ class tableMetadata: Object { @objc dynamic var e2eEncrypted: Bool = false @objc dynamic var edited: Bool = false @objc dynamic var etag = "" + @objc dynamic var etagResource = "" let exifPhotos = List() @objc dynamic var favorite: Bool = false @objc dynamic var fileId = "" @@ -82,7 +102,6 @@ class tableMetadata: Object { @objc public var lockOwnerDisplayName = "" @objc public var lockTime: Date? @objc public var lockTimeOut: Date? - @objc dynamic var mediaSearch: Bool = false @objc dynamic var path = "" @objc dynamic var permissions = "" @objc dynamic var placePhotos: String? @@ -92,15 +111,15 @@ class tableMetadata: Object { @objc dynamic var richWorkspace: String? @objc dynamic var sceneIdentifier: String? @objc dynamic var serverUrl = "" - @objc dynamic var serverUrlFileName = "" - @objc dynamic var destination = "" + @objc dynamic var serveUrlFileName = "" + @objc dynamic var serverUrlTo = "" @objc dynamic var session = "" @objc dynamic var sessionDate: Date? @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 @@ -123,8 +142,6 @@ class tableMetadata: Object { @objc dynamic var errorCode: Int = 0 @objc dynamic var nativeFormat: Bool = false @objc dynamic var autoUploadServerUrlBase: String? - @objc dynamic var typeIdentifier: String = "" - @objc dynamic var progress: Double = 0 override static func primaryKey() -> String { return "ocId" @@ -141,9 +158,6 @@ extension tableMetadata { } var isRenameable: Bool { - if !NCMetadataPermissions.canRename(self) { - return false - } if lock { return false } @@ -162,37 +176,38 @@ extension tableMetadata { } return false } - + var isSavebleInCameraRoll: Bool { - return (classFile == NKTypeClassFile.image.rawValue && contentType != "image/svg+xml") || classFile == NKTypeClassFile.video.rawValue + return (classFile == NKCommon.TypeClassFile.image.rawValue && contentType != "image/svg+xml") || classFile == NKCommon.TypeClassFile.video.rawValue } + var isDocumentViewableOnly: Bool { sharePermissionsCollaborationServices == NCPermissions().permissionReadShare && classFile == NKCommon.TypeClassFile.document.rawValue } var isAudioOrVideo: Bool { - return classFile == NKTypeClassFile.audio.rawValue || classFile == NKTypeClassFile.video.rawValue + return classFile == NKCommon.TypeClassFile.audio.rawValue || classFile == NKCommon.TypeClassFile.video.rawValue } var isImageOrVideo: Bool { - return classFile == NKTypeClassFile.image.rawValue || classFile == NKTypeClassFile.video.rawValue + return classFile == NKCommon.TypeClassFile.image.rawValue || classFile == NKCommon.TypeClassFile.video.rawValue } var isVideo: Bool { - return classFile == NKTypeClassFile.video.rawValue + return classFile == NKCommon.TypeClassFile.video.rawValue } var isAudio: Bool { - return classFile == NKTypeClassFile.audio.rawValue + return classFile == NKCommon.TypeClassFile.audio.rawValue } var isImage: Bool { - return classFile == NKTypeClassFile.image.rawValue + return classFile == NKCommon.TypeClassFile.image.rawValue } var isSavebleAsImage: Bool { - classFile == NKTypeClassFile.image.rawValue && contentType != "image/svg+xml" + classFile == NKCommon.TypeClassFile.image.rawValue && contentType != "image/svg+xml" } var isCopyableInPasteboard: Bool { @@ -210,31 +225,28 @@ extension tableMetadata { return isPDF || isImage } - var isCreatable: Bool { - if isDirectory { - return NCMetadataPermissions.canCreateFolder(self) - } else { - return NCMetadataPermissions.canCreateFile(self) - } - } - var isDeletable: Bool { - if (!isDirectoryE2EE && e2eEncrypted) || !NCMetadataPermissions.canDelete(self) { + if !isDirectoryE2EE && e2eEncrypted { return false } return true } var canSetAsAvailableOffline: Bool { - return session.isEmpty && !isDocumentViewableOnly //!isDirectoryE2EE && !e2eEncrypted +// return session.isEmpty && !isDocumentViewableOnly //!isDirectoryE2EE && !e2eEncrypted + return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted } var canShare: Bool { return session.isEmpty && !isDocumentViewableOnly && !directory && !NCBrandOptions.shared.disable_openin_file } + var canSetDirectoryAsE2EE: Bool { + return directory && size == 0 && !e2eEncrypted && NCKeychain().isEndToEndEnabled(account: account) + } + var canUnsetDirectoryAsE2EE: Bool { - return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) + return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCKeychain().isEndToEndEnabled(account: account) } var canOpenExternalEditor: Bool { @@ -276,7 +288,12 @@ extension tableMetadata { } @objc var isDirectoryE2EE: Bool { - return NCUtilityFileSystem().isDirectoryE2EE(serverUrl: serverUrl, urlBase: urlBase, userId: userId, account: account) + let session = NCSession.Session(account: account, urlBase: urlBase, user: user, userId: userId) + return NCUtilityFileSystem().isDirectoryE2EE(session: session, serverUrl: serverUrl) + } + + var isDirectoryE2EETop: Bool { + NCUtilityFileSystem().isDirectoryE2EETop(account: account, serverUrl: serverUrl) } var isLivePhoto: Bool { @@ -292,27 +309,21 @@ extension tableMetadata { } var hasPreviewBorder: Bool { - !isImage && !isAudioOrVideo && hasPreview && NCUtilityFileSystem().fileProviderStorageImageExists(ocId, etag: etag, ext: NCGlobal.shared.previewExt1024, userId: userId, urlBase: urlBase) + !isImage && !isAudioOrVideo && hasPreview && NCUtilityFileSystem().fileProviderStorageImageExists(ocId, etag: etag, ext: NCGlobal.shared.previewExt1024) } var isAvailableEditorView: Bool { guard !isPDF, - classFile == NKTypeClassFile.document.rawValue, - NextcloudKit.shared.isNetworkReachable() else { - return false - } + classFile == NKCommon.TypeClassFile.document.rawValue, + NextcloudKit.shared.isNetworkReachable() else { return false } let utility = NCUtility() - let directEditingEditors = utility.editorsDirectEditing(account: account, contentType: contentType).map { $0.lowercased() } + let directEditingEditors = utility.editorsDirectEditing(account: account, contentType: contentType) let richDocumentEditor = utility.isTypeFileRichDocument(self) - let capabilities = NCNetworking.shared.capabilities[account] - if let capabilities, - capabilities.richDocumentsEnabled, - richDocumentEditor, - directEditingEditors.isEmpty { + if NCCapabilities.shared.getCapabilities(account: account).capabilityRichDocumentsEnabled && richDocumentEditor && directEditingEditors.isEmpty { // RichDocument: Collabora return true - } else if directEditingEditors.contains("nextcloud text") || directEditingEditors.contains("onlyoffice") { + } else if directEditingEditors.contains(NCGlobal.shared.editorText) || directEditingEditors.contains(NCGlobal.shared.editorOnlyoffice) { // DirectEditing: Nextcloud Text - OnlyOffice return true } @@ -320,9 +331,8 @@ extension tableMetadata { } var isAvailableRichDocumentEditorView: Bool { - guard let capabilities = NCNetworking.shared.capabilities[account], - classFile == NKTypeClassFile.document.rawValue, - capabilities.richDocumentsEnabled, + guard classFile == NKCommon.TypeClassFile.document.rawValue, + NCCapabilities.shared.getCapabilities(account: account).capabilityRichDocumentsEnabled, NextcloudKit.shared.isNetworkReachable() else { return false } if NCUtility().isTypeFileRichDocument(self) { @@ -332,12 +342,10 @@ extension tableMetadata { } var isAvailableDirectEditingEditorView: Bool { - guard (classFile == NKTypeClassFile.document.rawValue) && NextcloudKit.shared.isNetworkReachable() else { - return false - } - let editors = NCUtility().editorsDirectEditing(account: account, contentType: contentType).map { $0.lowercased() } + guard (classFile == NKCommon.TypeClassFile.document.rawValue) && NextcloudKit.shared.isNetworkReachable() else { return false } + let editors = NCUtility().editorsDirectEditing(account: account, contentType: contentType) - if editors.contains("nextcloud text") || editors.contains("onlyoffice") { + if editors.contains(NCGlobal.shared.editorText) || editors.contains(NCGlobal.shared.editorOnlyoffice) { return true } return false @@ -354,39 +362,14 @@ extension tableMetadata { // Return if is sharable func isSharable() -> Bool { - guard let capabilities = NCNetworking.shared.capabilities[account] else { - return false - } - if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE) { -// if !NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingApiEnabled || (NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEEnabled && isDirectoryE2EE), !e2eEncrypted { + if !NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingApiEnabled || (NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEEnabled && isDirectoryE2EE), !e2eEncrypted { return false } return !e2eEncrypted } - - /// Returns a detached (unmanaged) deep copy of the current `tableMetadata` object. - /// - /// - Note: The Realm `List` properties containing primitive types (e.g., `tags`, `shareType`) are copied automatically - /// by the Realm initializer `init(value:)`. For `List` containing Realm objects (e.g., `exifPhotos`), this method - /// creates new instances to ensure the copy is fully detached and safe to use outside of a Realm context. - /// - /// - Returns: A new `tableMetadata` instance fully detached from Realm. - func detachedCopy() -> tableMetadata { - // Use Realm's built-in copy constructor for primitive properties and List of primitives - let detached = tableMetadata(value: self) - - // Deep copy of List of Realm objects (exifPhotos) - detached.exifPhotos.removeAll() - detached.exifPhotos.append(objectsIn: self.exifPhotos.map { NCKeyValue(value: $0) }) - - return detached - } } extension NCManageDatabase { - - // MARK: - Create Metadata - func convertFileToMetadata(_ file: NKFile, isDirectoryE2EE: Bool) -> tableMetadata { let metadata = tableMetadata() @@ -456,7 +439,9 @@ 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) } @@ -516,14 +501,14 @@ extension NCManageDatabase { let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) if counter == 0 && useFirstAsMetadataFolder { - metadataFolder = metadata + metadataFolder = tableMetadata(value: metadata) } else { metadatas.append(metadata) } counter += 1 } - completion(tableMetadata(value: metadataFolder), metadatas) + completion(metadataFolder, metadatas) } func convertFilesToMetadatas(_ files: [NKFile], useMetadataFolder: Bool, completion: @escaping (_ metadataFolder: tableMetadata, _ metadatasFolder: [tableMetadata], _ metadatas: [tableMetadata]) -> Void) { @@ -562,14 +547,42 @@ extension NCManageDatabase { completion(metadataFolder, metadataFolders, metadatas) } - func convertFilesToMetadatasAsync(_ files: [NKFile], useFirstAsMetadataFolder: Bool) async -> (metadataFolder: tableMetadata, metadatas: [tableMetadata]) { - await withCheckedContinuation { continuation in - convertFilesToMetadatas(files, useFirstAsMetadataFolder: useFirstAsMetadataFolder) { metadataFolder, metadatas in - continuation.resume(returning: (metadataFolder, metadatas)) - } - } - } - +// func convertFilesToMetadatas(_ files: [NKFile], useMetadataFolder: Bool, completion: @escaping (_ metadataFolder: tableMetadata, _ metadatasFolder: [tableMetadata], _ metadatas: [tableMetadata]) -> Void) { +// +// var counter: Int = 0 +// var isDirectoryE2EE: Bool = false +// let listServerUrl = ThreadSafeDictionary() +// +// var metadataFolder = tableMetadata() +// var metadataFolders: [tableMetadata] = [] +// var metadatas: [tableMetadata] = [] +// +// for file in files { +// +// if let key = listServerUrl[file.serverUrl] { +// isDirectoryE2EE = key +// } else { +// isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file) +// listServerUrl[file.serverUrl] = isDirectoryE2EE +// } +// +// let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) +// +// if counter == 0 && useMetadataFolder { +// metadataFolder = tableMetadata.init(value: metadata) +// } else { +// metadatas.append(metadata) +// if metadata.directory { +// metadataFolders.append(metadata) +// } +// } +// +// counter += 1 +// } +// +// completion(metadataFolder, metadataFolders, metadatas) +// } + func getMetadataDirectoryFrom(files: [NKFile]) -> tableMetadata? { guard let file = files.first else { return nil } let isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file) @@ -644,15 +657,16 @@ extension NCManageDatabase { } func isMetadataShareOrMounted(metadata: tableMetadata, metadataFolder: tableMetadata?) -> Bool { + let permissions = NCPermissions() var isShare = false var isMounted = false if metadataFolder != nil { - isShare = metadata.permissions.contains(NCMetadataPermissions.permissionShared) && !metadataFolder!.permissions.contains(NCMetadataPermissions.permissionShared) - isMounted = metadata.permissions.contains(NCMetadataPermissions.permissionMounted) && !metadataFolder!.permissions.contains(NCMetadataPermissions.permissionMounted) + isShare = metadata.permissions.contains(permissions.permissionShared) && !metadataFolder!.permissions.contains(permissions.permissionShared) + isMounted = metadata.permissions.contains(permissions.permissionMounted) && !metadataFolder!.permissions.contains(permissions.permissionMounted) } else if let directory = getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrl)) { - isShare = metadata.permissions.contains(NCMetadataPermissions.permissionShared) && !directory.permissions.contains(NCMetadataPermissions.permissionShared) - isMounted = metadata.permissions.contains(NCMetadataPermissions.permissionMounted) && !directory.permissions.contains(NCMetadataPermissions.permissionMounted) + isShare = metadata.permissions.contains(permissions.permissionShared) && !directory.permissions.contains(permissions.permissionShared) + isMounted = metadata.permissions.contains(permissions.permissionMounted) && !directory.permissions.contains(permissions.permissionMounted) } if isShare || isMounted { @@ -662,721 +676,514 @@ extension NCManageDatabase { } } - // MARK: - Realm Write + // MARK: - Set - func addMetadataIfNeededAsync(_ metadata: tableMetadata, sync: Bool = true) { - let detached = metadata.detachedCopy() - - performRealmWrite(sync: sync) { realm in - if realm.object(ofType: tableMetadata.self, forPrimaryKey: metadata.ocId) == nil { - realm.add(detached) + @discardableResult + func addMetadata(_ metadata: tableMetadata) -> tableMetadata { + do { + let realm = try Realm() + try realm.write { + return tableMetadata(value: realm.create(tableMetadata.self, value: metadata, update: .all)) } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } - } - - func addAndReturnMetadata(_ metadata: tableMetadata) -> tableMetadata? { - let detached = metadata.detachedCopy() - - performRealmWrite { realm in - realm.add(detached, update: .all) - } - - return performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter("ocId == %@", metadata.ocId) - .first - .map { $0.detachedCopy() } - } - } - - func addAndReturnMetadataAsync(_ metadata: tableMetadata) async -> tableMetadata? { - let detached = metadata.detachedCopy() - - await performRealmWriteAsync { realm in - realm.add(detached, update: .all) - } - - return await performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter("ocId == %@", metadata.ocId) - .first? - .detachedCopy() - } - } - - func addMetadata(_ metadata: tableMetadata, sync: Bool = true) { - let detached = metadata.detachedCopy() - - performRealmWrite(sync: sync) { realm in - realm.add(detached, update: .all) - } - } - func addMetadataAsync(_ metadata: tableMetadata) async { - let detached = metadata.detachedCopy() - - await performRealmWriteAsync { realm in - realm.add(detached, update: .all) - } + return tableMetadata(value: metadata) } - func addMetadatas(_ metadatas: [tableMetadata], sync: Bool = true) { - let detached = metadatas.map { $0.detachedCopy() } - - performRealmWrite(sync: sync) { realm in - realm.add(detached, update: .all) - } - } - - func addMetadatasAsync(_ metadatas: [tableMetadata]) async { - let detached = metadatas.map { $0.detachedCopy() } - - await performRealmWriteAsync { realm in - realm.add(detached, update: .all) + func addMetadatas(_ metadatas: [tableMetadata]) { + do { + let realm = try Realm() + try realm.write { + for metadata in metadatas { + realm.create(tableMetadata.self, value: metadata, update: .all) + } + } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func deleteMetadataAsync(predicate: NSPredicate) async { - await performRealmWriteAsync { realm in - let result = realm.objects(tableMetadata.self) - .filter(predicate) - realm.delete(result) + func deleteMetadata(predicate: NSPredicate) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter(predicate) + realm.delete(results) + } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func deleteMetadataAsync(id: String?) async { - guard let id else { return } + func deleteMetadataOcId(_ ocId: String?) { + guard let ocId else { return } - await performRealmWriteAsync { realm in - let result = realm.objects(tableMetadata.self) - .filter("ocId == %@ OR fileId == %@", id, id) - realm.delete(result) + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter("ocId == %@", ocId) + realm.delete(results) + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } } - func deleteMetadataOcIds(_ ocIds: [String], sync: Bool = true) { - performRealmWrite(sync: sync) { realm in - let result = realm.objects(tableMetadata.self) - .filter("ocId IN %@", ocIds) - realm.delete(result) + func deleteMetadataOcIds(_ ocIds: [String]) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter("ocId IN %@", ocIds) + realm.delete(results) + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } } - func replaceMetadataAsync(id: String, metadata: tableMetadata) async { - let detached = metadata.detachedCopy() - - await performRealmWriteAsync { realm in - let result = realm.objects(tableMetadata.self) - .filter("ocId == %@ OR ocIdTransfer == %@", id, id) - realm.delete(result) - realm.add(detached, update: .all) + func deleteMetadatas(_ metadatas: [tableMetadata]) { + do { + let realm = try Realm() + try realm.write { + realm.delete(metadatas) + } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - // Asynchronously deletes an array of `tableMetadata` entries from the Realm database. - /// - Parameter metadatas: The `tableMetadata` objects to be deleted. - func deleteMetadatasAsync(_ metadatas: [tableMetadata]) async { - guard !metadatas.isEmpty else { - return - } - let detached = metadatas.map { $0.detachedCopy() } + func renameMetadata(fileNameNew: String, ocId: String, status: Int = NCGlobal.shared.metadataStatusNormal) { + do { + let realm = try Realm() + try realm.write { + if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first { + let fileNameView = result.fileNameView + let fileIdMOV = result.livePhotoFile + let directoryServerUrl = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: fileNameView) + let resultsType = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameNew, mimeType: "", directory: result.directory, account: result.account) + + result.fileName = fileNameNew + result.fileNameView = fileNameNew + result.iconName = resultsType.iconName + result.contentType = resultsType.mimeType + result.classFile = resultsType.classFile + result.status = status + + if status == NCGlobal.shared.metadataStatusNormal { + result.sessionDate = nil + } else { + result.sessionDate = Date() + } - await performRealmWriteAsync { realm in - for detached in detached { - if let managed = realm.object(ofType: tableMetadata.self, forPrimaryKey: detached.ocId) { - realm.delete(managed) - } - } - } - } + if result.directory, + let resultDirectory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", result.account, directoryServerUrl).first { + let serverUrlTo = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: fileNameNew) - func renameMetadata(fileNameNew: String, ocId: String, status: Int = NCGlobal.shared.metadataStatusNormal) async { - await performRealmWriteAsync { realm in - guard let metadata = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first else { - return - } + resultDirectory.serverUrl = serverUrlTo + } else { + let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId) + "/" + fileNameView + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId) + "/" + fileNameNew - let oldFileNameView = metadata.fileNameView - let account = metadata.account - let originalServerUrl = metadata.serverUrl + self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) + } - metadata.fileName = fileNameNew - metadata.fileNameView = fileNameNew - metadata.status = status - metadata.sessionDate = (status == NCGlobal.shared.metadataStatusNormal) ? nil : Date() + if result.isLivePhoto, + let resultMOV = realm.objects(tableMetadata.self).filter("fileId == %@ AND account == %@", fileIdMOV, result.account).first { + let fileNameView = resultMOV.fileNameView + let fileName = (fileNameNew as NSString).deletingPathExtension + let ext = (resultMOV.fileName as NSString).pathExtension + resultMOV.fileName = fileName + "." + ext + resultMOV.fileNameView = fileName + "." + ext - if metadata.directory { - let oldDirUrl = self.utilityFileSystem.createServerUrl(serverUrl: originalServerUrl, fileName: oldFileNameView) - let newDirUrl = self.utilityFileSystem.createServerUrl(serverUrl: originalServerUrl, fileName: fileNameNew) + let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId) + "/" + fileNameView + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId) + "/" + fileName + "." + ext - if let dir = realm.objects(tableDirectory.self) - .filter("account == %@ AND serverUrl == %@", account, oldDirUrl) - .first { - dir.serverUrl = newDirUrl + self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) + } } - } else { - let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, userId: metadata.userId, urlBase: metadata.urlBase) + "/" + oldFileNameView - let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, userId: metadata.userId, urlBase: metadata.urlBase) + "/" + fileNameNew - self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func restoreMetadataFileName(ocId: String, sync: Bool = true) { - performRealmWrite(sync: sync) { realm in - if let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first, - let encodedURLString = result.serverUrlFileName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), - let url = URL(string: encodedURLString) { - let fileIdMOV = result.livePhotoFile - let directoryServerUrl = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: result.fileNameView) - let lastPathComponent = url.lastPathComponent - let fileName = lastPathComponent.removingPercentEncoding ?? lastPathComponent - let fileNameView = result.fileNameView - - result.fileName = fileName - result.fileNameView = fileName - result.status = NCGlobal.shared.metadataStatusNormal - result.sessionDate = nil + func restoreMetadataFileName(ocId: String) { + do { + let realm = try Realm() + try realm.write { + if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first, + let encodedURLString = result.serveUrlFileName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), + let url = URL(string: encodedURLString) { + let fileIdMOV = result.livePhotoFile + let directoryServerUrl = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: result.fileNameView) + let lastPathComponent = url.lastPathComponent + let fileName = lastPathComponent.removingPercentEncoding ?? lastPathComponent + let fileNameView = result.fileNameView + + result.fileName = fileName + result.fileNameView = fileName + result.status = NCGlobal.shared.metadataStatusNormal + result.sessionDate = nil - if result.directory, - let resultDirectory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", result.account, directoryServerUrl).first { - let serverUrlTo = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: fileName) + if result.directory, + let resultDirectory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", result.account, directoryServerUrl).first { + let serverUrlTo = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: fileName) - resultDirectory.serverUrl = serverUrlTo - } else { - let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId, userId: result.userId, urlBase: result.urlBase) + "/" + fileNameView - let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId, userId: result.userId, urlBase: result.urlBase) + "/" + fileName + resultDirectory.serverUrl = serverUrlTo + } else { + let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId) + "/" + fileNameView + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId) + "/" + fileName - self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) - } + self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) + } - if result.isLivePhoto, - let resultMOV = realm.objects(tableMetadata.self).filter("fileId == %@ AND account == %@", fileIdMOV, result.account).first { - let fileNameView = resultMOV.fileNameView - let fileName = (fileName as NSString).deletingPathExtension - let ext = (resultMOV.fileName as NSString).pathExtension - resultMOV.fileName = fileName + "." + ext - resultMOV.fileNameView = fileName + "." + ext + if result.isLivePhoto, + let resultMOV = realm.objects(tableMetadata.self).filter("fileId == %@ AND account == %@", fileIdMOV, result.account).first { + let fileNameView = resultMOV.fileNameView + let fileName = (fileName as NSString).deletingPathExtension + let ext = (resultMOV.fileName as NSString).pathExtension + resultMOV.fileName = fileName + "." + ext + resultMOV.fileNameView = fileName + "." + ext - let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId, userId: resultMOV.userId, urlBase: resultMOV.urlBase) + "/" + fileNameView - let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId, userId: resultMOV.userId, urlBase: resultMOV.urlBase) + "/" + fileName + "." + ext + let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId) + "/" + fileNameView + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId) + "/" + fileName + "." + ext - self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) + self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) + } } } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - /// Asynchronously restores the file name of a metadata entry and updates related file system and Realm entries. - /// - Parameter ocId: The object ID (ocId) of the file to restore. - func restoreMetadataFileNameAsync(ocId: String) async { - await performRealmWriteAsync { realm in - guard let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first, - let encodedURLString = result.serverUrlFileName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), - let url = URL(string: encodedURLString) - else { - return - } - - let fileIdMOV = result.livePhotoFile - let directoryServerUrl = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: result.fileNameView) - let lastPathComponent = url.lastPathComponent - let fileName = lastPathComponent.removingPercentEncoding ?? lastPathComponent - let fileNameView = result.fileNameView - - result.fileName = fileName - result.fileNameView = fileName - result.status = NCGlobal.shared.metadataStatusNormal - result.sessionDate = nil - - if result.directory, - let resultDirectory = realm.objects(tableDirectory.self) - .filter("account == %@ AND serverUrl == %@", result.account, directoryServerUrl) - .first { - let serverUrlTo = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: fileName) - resultDirectory.serverUrl = serverUrlTo - } else { - let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId, userId: result.userId, urlBase: result.urlBase) + "/" + fileNameView - let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId, userId: result.userId, urlBase: result.urlBase) + "/" + fileName - self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) - } - - if result.isLivePhoto, - let resultMOV = realm.objects(tableMetadata.self) - .filter("fileId == %@ AND account == %@", fileIdMOV, result.account) - .first { - let fileNameViewMOV = resultMOV.fileNameView - let baseName = (fileName as NSString).deletingPathExtension - let ext = (resultMOV.fileName as NSString).pathExtension - let fullFileName = baseName + "." + ext - - resultMOV.fileName = fullFileName - resultMOV.fileNameView = fullFileName - - let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId, userId: resultMOV.userId, urlBase: resultMOV.urlBase) + "/" + fileNameViewMOV - let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId, userId: resultMOV.userId, urlBase: resultMOV.urlBase) + "/" + fullFileName - self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) + func setMetadataServeUrlFileNameStatusNormal(ocId: String) { + do { + let realm = try Realm() + try realm.write { + if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first { + result.serveUrlFileName = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: result.fileName) + result.status = NCGlobal.shared.metadataStatusNormal + result.sessionDate = nil + } } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataServerUrlFileNameStatusNormal(ocId: String, sync: Bool = true) { - performRealmWrite(sync: sync) { realm in - if let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first { - result.serverUrlFileName = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: result.fileName) - result.status = NCGlobal.shared.metadataStatusNormal - result.sessionDate = nil - } - } - } + func setMetadataEtagResource(ocId: String, etagResource: String?) { + guard let etagResource else { return } - func setMetadataServerUrlFileNameStatusNormalAsync(ocId: String) async { - await performRealmWriteAsync { realm in - if let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first { - result.serverUrlFileName = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: result.fileName) - result.status = NCGlobal.shared.metadataStatusNormal - result.sessionDate = nil + do { + let realm = try Realm() + try realm.write { + let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first + result?.etagResource = etagResource } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataLivePhotoByServerAsync(account: String, - ocId: String, - livePhotoFile: String) async { - await performRealmWriteAsync { realm in - if let result = realm.objects(tableMetadata.self) - .filter("account == %@ AND ocId == %@", account, ocId) - .first { - result.isFlaggedAsLivePhotoByServer = true - result.livePhotoFile = livePhotoFile + func setMetadataLivePhotoByServer(account: String, ocId: String, livePhotoFile: String) { + do { + let realm = try Realm() + try realm.write { + if let result = realm.objects(tableMetadata.self).filter("account == %@ AND ocId == %@", account, ocId).first { + result.isFlaggedAsLivePhotoByServer = true + result.livePhotoFile = livePhotoFile + } } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func updateMetadatasFavoriteAsync(account: String, metadatas: [tableMetadata]) async { - guard !metadatas.isEmpty else { return } - - await performRealmWriteAsync { realm in - let oldFavorites = realm.objects(tableMetadata.self) - .filter("account == %@ AND favorite == true", account) - for item in oldFavorites { - item.favorite = false - } - realm.add(metadatas, update: .all) - } - } - - /// Asynchronously updates a list of `tableMetadata` entries in Realm for a given account and server URL. - /// - /// This function performs the following steps: - /// 1. Skips all entries with `status != metadataStatusNormal`. - /// 2. Deletes existing metadata entries with `status == metadataStatusNormal` that are not in the skip list. - /// 3. Copies matching `mediaSearch` from previously deleted metadata to the incoming list. - /// 4. Inserts or updates new metadata entries into Realm, except those in the skip list. - /// - /// - Parameters: - /// - metadatas: An array of incoming detached `tableMetadata` objects to insert or update. - /// - serverUrl: The server URL associated with the metadata entries. - /// - account: The account identifier used to scope the metadata update. - func updateMetadatasFilesAsync(_ metadatas: [tableMetadata], serverUrl: String, account: String) async { - await performRealmWriteAsync { realm in - let ocIdsToSkip = Set( - realm.objects(tableMetadata.self) - .filter("status != %d", NCGlobal.shared.metadataStatusNormal) - .map(\.ocId) - ) - - let resultsToDelete = realm.objects(tableMetadata.self) - .filter("account == %@ AND serverUrl == %@ AND status == %d AND fileName != %@", account, serverUrl, NCGlobal.shared.metadataStatusNormal, NextcloudKit.shared.nkCommonInstance.rootFileName) - .filter { !ocIdsToSkip.contains($0.ocId) } - let metadatasCopy = Array(resultsToDelete).map { tableMetadata(value: $0) } - - realm.delete(resultsToDelete) - - for metadata in metadatas { - guard !ocIdsToSkip.contains(metadata.ocId) else { - continue - } - if let match = metadatasCopy.first(where: { $0.ocId == metadata.ocId }) { - metadata.mediaSearch = match.mediaSearch + func updateMetadatasFavorite(account: String, metadatas: [tableMetadata]) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter("account == %@ AND favorite == true", account) + for result in results { + result.favorite = false } - realm.add(metadata.detachedCopy(), update: .all) + realm.add(metadatas, update: .all) } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataEncrypted(ocId: String, encrypted: Bool, sync: Bool = true) { - performRealmWrite(sync: sync) { realm in - let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first - result?.e2eEncrypted = encrypted - } - } - - func setMetadataEncryptedAsync(ocId: String, encrypted: Bool) async { - await performRealmWriteAsync { realm in - let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first - result?.e2eEncrypted = encrypted - } - } - - func setMetadataFileNameView(serverUrl: String, fileName: String, newFileNameView: String, account: String, sync: Bool = true) { - performRealmWrite(sync: sync) { realm in - let result = realm.objects(tableMetadata.self) - .filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName) - .first - result?.fileNameView = newFileNameView - } - } - - func setMetadataFileNameViewAsync(serverUrl: String, fileName: String, newFileNameView: String, account: String) async { - await performRealmWriteAsync { realm in - let result = realm.objects(tableMetadata.self) - .filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName) - .first - result?.fileNameView = newFileNameView - } - } - - func moveMetadata(ocId: String, serverUrlTo: String, sync: Bool = true) { - performRealmWrite(sync: sync) { realm in - if let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first { - result.serverUrl = serverUrlTo + func updateMetadatasFiles(_ metadatas: [tableMetadata], serverUrl: String, account: String) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND status == %d", account, serverUrl, NCGlobal.shared.metadataStatusNormal)) + realm.delete(results) + for metadata in metadatas { + if realm.objects(tableMetadata.self).filter(NSPredicate(format: "ocId == %@ AND status != %d", metadata.ocId, NCGlobal.shared.metadataStatusNormal)).first != nil { + continue + } + realm.add(tableMetadata(value: metadata), update: .all) + } } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func moveMetadataAsync(ocId: String, serverUrlTo: String) async { - await performRealmWriteAsync { realm in - if let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first { - result.serverUrl = serverUrlTo + func setMetadataEncrypted(ocId: String, encrypted: Bool) { + do { + let realm = try Realm() + try realm.write { + let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first + result?.e2eEncrypted = encrypted } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setLivePhotoFile(fileId: String, livePhotoFile: String) async { - await performRealmWriteAsync { realm in - let result = realm.objects(tableMetadata.self) - .filter("fileId == %@", fileId) - .first - result?.livePhotoFile = livePhotoFile - } - } - - func clearAssetLocalIdentifiersAsync(_ assetLocalIdentifiers: [String]) async { - await performRealmWriteAsync { realm in - let results = realm.objects(tableMetadata.self) - .filter("assetLocalIdentifier IN %@", assetLocalIdentifiers) - for result in results { - result.assetLocalIdentifier = "" + func setMetadataFileNameView(serverUrl: String, fileName: String, newFileNameView: String, account: String) { + do { + let realm = try Realm() + try realm.write { + let result = realm.objects(tableMetadata.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName).first + result?.fileNameView = newFileNameView } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataFavorite(ocId: String, favorite: Bool?, saveOldFavorite: String?, status: Int, sync: Bool = true) { - performRealmWrite(sync: sync) { realm in - let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first - if let favorite { - result?.favorite = favorite - } - result?.storeFlag = saveOldFavorite - result?.status = status - - if status == NCGlobal.shared.metadataStatusNormal { - result?.sessionDate = nil - } else { - result?.sessionDate = Date() + func moveMetadata(ocId: String, serverUrlTo: String) { + do { + let realm = try Realm() + try realm.write { + if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first { + result.serverUrl = serverUrlTo + } } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - /// Asynchronously sets the favorite status of a `tableMetadata` entry. - /// Optionally stores the previous favorite flag and updates the sync status. - func setMetadataFavoriteAsync(ocId: String, favorite: Bool?, saveOldFavorite: String?, status: Int) async { - await performRealmWriteAsync { realm in - guard let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first else { - return - } - - if let favorite { - result.favorite = favorite + func clearAssetLocalIdentifiers(_ assetLocalIdentifiers: [String]) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter("assetLocalIdentifier IN %@", assetLocalIdentifiers) + for result in results { + result.assetLocalIdentifier = "" + } } - - result.storeFlag = saveOldFavorite - result.status = status - result.sessionDate = (status == NCGlobal.shared.metadataStatusNormal) ? nil : Date() + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataCopyMove(ocId: String, serverUrlTo: String, overwrite: String?, status: Int, sync: Bool = true) { - performRealmWrite(sync: sync) { realm in - if let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first { - result.destination = serverUrlTo - result.storeFlag = overwrite - result.status = status + func setMetadataFavorite(ocId: String, favorite: Bool?, saveOldFavorite: String?, status: Int) { + do { + let realm = try Realm() + try realm.write { + let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first + if let favorite { + result?.favorite = favorite + } + result?.storeFlag = saveOldFavorite + result?.status = status if status == NCGlobal.shared.metadataStatusNormal { - result.sessionDate = nil + result?.sessionDate = nil } else { - result.sessionDate = Date() + result?.sessionDate = Date() } } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - /// Asynchronously updates a `tableMetadata` entry to set copy/move status and target server URL. - func setMetadataCopyMoveAsync(ocId: String, destination: String, overwrite: String?, status: Int) async { - await performRealmWriteAsync { realm in - guard let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first else { - return - } - - result.destination = destination - result.storeFlag = overwrite - result.status = status - result.sessionDate = (status == NCGlobal.shared.metadataStatusNormal) ? nil : Date() - } - } - - func clearMetadatasUpload(account: String, sync: Bool = true) { - performRealmWrite(sync: sync) { realm in - let results = realm.objects(tableMetadata.self) - .filter("account == %@ AND (status == %d OR status == %d)", account, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploadError) - realm.delete(results) - } - } - - func clearMetadatasUploadAsync(account: String) async { - await performRealmWriteAsync { realm in - let results = realm.objects(tableMetadata.self) - .filter("account == %@ AND (status == %d OR status == %d)", account, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploadError) - realm.delete(results) - } - } - - /// Syncs the remote and local metadata. - /// Returns true if there were changes (additions or deletions), false if everything was already up-to-date. - func mergeRemoteMetadatasAsync(remoteMetadatas: [tableMetadata], localMetadatas: [tableMetadata]) async -> Bool { - // Set of ocId - let remoteOcIds = Set(remoteMetadatas.map { $0.ocId }) - let localOcIds = Set(localMetadatas.map { $0.ocId }) - - // Calculate diffs - let toDeleteOcIds = localOcIds.subtracting(remoteOcIds) - let toAddOcIds = remoteOcIds.subtracting(localOcIds) - - guard !toDeleteOcIds.isEmpty || !toAddOcIds.isEmpty else { - return false // No changes needed - } - - let toDeleteKeys = Array(toDeleteOcIds) + func setMetadataCopyMove(ocId: String, serverUrlTo: String, overwrite: String?, status: Int) { + do { + let realm = try Realm() + try realm.write { + let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first + result?.serverUrlTo = serverUrlTo + result?.storeFlag = overwrite + result?.status = status - await performRealmWriteAsync { realm in - let toAdd = remoteMetadatas.filter { toAddOcIds.contains($0.ocId) } - let toDelete = toDeleteKeys.compactMap { - realm.object(ofType: tableMetadata.self, forPrimaryKey: $0) + if status == NCGlobal.shared.metadataStatusNormal { + result?.sessionDate = nil + } else { + result?.sessionDate = Date() + } } - - realm.delete(toDelete) - realm.add(toAdd, update: .modified) + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } - - return true } - // MARK: - Realm Read - - func getAllTableMetadataAsync() async -> [tableMetadata] { - return await performRealmReadAsync { realm in - realm.objects(tableMetadata.self).map { tableMetadata(value: $0) } - } ?? [] - } + // MARK: - GetMetadata func getMetadata(predicate: NSPredicate) -> tableMetadata? { - return performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .first - .map { $0.detachedCopy() } + do { + let realm = try Realm() + guard let result = realm.objects(tableMetadata.self).filter(predicate).first else { return nil } + return tableMetadata(value: result) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } - } - func getMetadataAsync(predicate: NSPredicate) async -> tableMetadata? { - return await performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .first - .map { $0.detachedCopy() } - } + return nil } func getMetadatas(predicate: NSPredicate) -> [tableMetadata] { - performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .map { $0.detachedCopy() } - } ?? [] + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter(predicate) + return Array(results.map { tableMetadata(value: $0) }) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return [] } - func getMetadatas(predicate: NSPredicate, - sortedByKeyPath: String, - ascending: Bool = false) -> [tableMetadata]? { - return performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .sorted(byKeyPath: sortedByKeyPath, ascending: ascending) - .map { $0.detachedCopy() } + func getMetadatas(predicate: NSPredicate, sortedByKeyPath: String, ascending: Bool = false) -> [tableMetadata]? { + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sortedByKeyPath, ascending: ascending) + return Array(results.map { tableMetadata(value: $0) }) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return nil } - func getMetadatasAsync(predicate: NSPredicate, - sortedByKeyPath: String, - ascending: Bool = false, - limit: Int? = nil) async -> [tableMetadata]? { - return await performRealmReadAsync { realm in - let results = realm.objects(tableMetadata.self) - .filter(predicate) - .sorted(byKeyPath: sortedByKeyPath, - ascending: ascending) - - if let limit { - let sliced = results.prefix(limit) - return sliced.map { $0.detachedCopy() } - } else { - return results.map { $0.detachedCopy() } + func getMetadatas(predicate: NSPredicate, numItems: Int, sorted: String, ascending: Bool) -> [tableMetadata] { + var counter: Int = 0 + var metadatas: [tableMetadata] = [] + + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending) + for result in results where counter < numItems { + metadatas.append(tableMetadata(value: result)) + counter += 1 } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } - } - - func getMetadatas(predicate: NSPredicate, - numItems: Int, - sorted: String, - ascending: Bool) -> [tableMetadata] { - return performRealmRead { realm in - let results = realm.objects(tableMetadata.self) - .filter(predicate) - .sorted(byKeyPath: sorted, ascending: ascending) - return results.prefix(numItems) - .map { $0.detachedCopy() } - } ?? [] + return metadatas } func getMetadataFromOcId(_ ocId: String?) -> tableMetadata? { guard let ocId else { return nil } - return performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first - .map { $0.detachedCopy() } + do { + let realm = try Realm() + guard let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first else { return nil } + return tableMetadata(value: result) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return nil } - func getMetadataFromOcIdAsync(_ ocId: String?) async -> tableMetadata? { +// func getMetadataFromOcIdAndocIdTransfer(_ ocId: String?) -> tableMetadata? { +// guard let ocId else { return nil } +// +// do { +// let realm = try Realm() +// if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first { +// return tableMetadata(value: result) +// } +// if let result = realm.objects(tableMetadata.self).filter("ocIdTransfer == %@", ocId).first { +// return tableMetadata(value: result) +// } +// } catch let error as NSError { +// NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") +// } +// return nil +// } + + func getMetadataFromOcIdAndocIdTransfer(_ ocId: String?) -> tableMetadata? { guard let ocId else { return nil } - return await performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first - .map { $0.detachedCopy() } - } - } - - func getMetadataFromOcIdAndocIdTransferAsync(_ ocId: String?) async -> tableMetadata? { - guard let ocId else { - return nil - } - - return await performRealmReadAsync { realm in + return performRealmRead { realm in realm.objects(tableMetadata.self) .filter("ocId == %@ OR ocIdTransfer == %@", ocId, ocId) .first - .map { $0.detachedCopy() } + .map { tableMetadata(value: $0) } } } - - /// Asynchronously retrieves the metadata for a folder, based on its session and serverUrl. - /// Handles the home directory case rootFileName) and detaches the Realm object before returning. - func getMetadataFolderAsync(session: NCSession.Session, serverUrl: String) async -> tableMetadata? { + +// func getMetadataFromOcIdAndocIdTransfer(_ ocId: String?) -> tableMetadata? { +// guard let ocId else { return nil } +// +// return performRealmRead { realm in +// // First, try to find by ocId +// if let result = realm.objects(tableMetadata.self) +// .filter("ocId == %@", ocId) +// .first { +// return tableMetadata(value: result) +// } +// +// // If not found, try to find by ocIdTransfer +// if let result = realm.objects(tableMetadata.self) +// .filter("ocIdTransfer == %@", ocId) +// .first { +// return tableMetadata(value: result) +// } +// +// // Not found +// return nil +// } +// } + + func getMetadataFolder(session: NCSession.Session, serverUrl: String) -> tableMetadata? { var serverUrl = serverUrl var fileName = "" - let home = utilityFileSystem.getHomeServer(session: session) + let serverUrlHome = utilityFileSystem.getHomeServer(session: session) - if home == serverUrl { - fileName = NextcloudKit.shared.nkCommonInstance.rootFileName + if serverUrlHome == serverUrl { + fileName = "." + serverUrl = ".." } else { fileName = (serverUrl as NSString).lastPathComponent - if let serverDirectoryUp = utilityFileSystem.serverDirectoryUp(serverUrl: serverUrl, home: home) { - serverUrl = serverDirectoryUp + if let path = utilityFileSystem.deleteLastPath(serverUrlPath: serverUrl) { + serverUrl = path } } - return await performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter("account == %@ AND serverUrl == %@ AND fileName == %@", session.account, serverUrl, fileName) - .first - .map { $0.detachedCopy() } + do { + let realm = try Realm() + guard let result = realm.objects(tableMetadata.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@", session.account, serverUrl, fileName).first else { return nil } + return tableMetadata(value: result) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return nil } func getMetadataLivePhoto(metadata: tableMetadata) -> tableMetadata? { - guard metadata.isLivePhoto else { - return nil - } - let detached = metadata.detachedCopy() - - return performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileId == %@", - detached.account, - detached.serverUrl, - detached.livePhotoFile)) - .first - .map { $0.detachedCopy() } - } - } + guard metadata.isLivePhoto else { return nil } - func getMetadataLivePhotoAsync(metadata: tableMetadata) async -> tableMetadata? { - guard metadata.isLivePhoto else { - return nil - } - let detached = metadata.detachedCopy() - - return await performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileId == %@", - detached.account, - detached.serverUrl, - detached.livePhotoFile)) - .first - .map { $0.detachedCopy() } + do { + let realm = try Realm() + guard let result = realm.objects(tableMetadata.self).filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileId == %@", + metadata.account, + metadata.serverUrl, + metadata.livePhotoFile)).first else { return nil } + return tableMetadata(value: result) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return nil } func getMetadataConflict(account: String, serverUrl: String, fileNameView: String, nativeFormat: Bool) -> tableMetadata? { @@ -1393,97 +1200,122 @@ extension NCManageDatabase { fileNameConflict)) } - /// Asynchronously retrieves and sorts `tableMetadata` associated with groupfolders for a given session. - /// - Parameters: - /// - session: The `NCSession.Session` containing account and server information. - /// - layoutForView: An optional layout configuration used for sorting. - /// - Returns: An array of sorted and detached `tableMetadata` objects. - func getMetadatasFromGroupfoldersAsync(session: NCSession.Session, layoutForView: NCDBLayoutForView?) async -> [tableMetadata] { - let homeServerUrl = utilityFileSystem.getHomeServer(session: session) - - let detachedMetadatas: [tableMetadata] = await performRealmReadAsync { realm in - var ocIds: [String] = [] - - // Safely fetch and detach groupfolders - let groupfolders = realm.objects(TableGroupfolders.self) - .filter("account == %@", session.account) - .sorted(byKeyPath: "mountPoint", ascending: true) - .map { TableGroupfolders(value: $0) } - - for groupfolder in groupfolders { - let mountPoint = groupfolder.mountPoint.hasPrefix("/") ? groupfolder.mountPoint : "/" + groupfolder.mountPoint - let serverUrlFileName = homeServerUrl + mountPoint + // MARK: - GetResult(s)Metadata - if let directory = realm.objects(tableDirectory.self) - .filter("account == %@ AND serverUrl == %@", session.account, serverUrlFileName) - .first, - let metadata = realm.objects(tableMetadata.self) - .filter("ocId == %@", directory.ocId) - .first { - ocIds.append(metadata.ocId) + func getResultsMetadatasPredicate(_ predicate: NSPredicate, layoutForView: NCDBLayoutForView?, directoryOnTop: Bool = true) -> [tableMetadata] { + do { + let realm = try Realm() + var results = realm.objects(tableMetadata.self).filter(predicate).freeze() + let layout: NCDBLayoutForView = layoutForView ?? NCDBLayoutForView() + + if layout.sort == "fileName" { + let sortedResults = results.sorted { + let ordered = layout.ascending ? ComparisonResult.orderedAscending : ComparisonResult.orderedDescending + // 1. favorite order + if $0.favorite == $1.favorite { + // 2. directory order TOP + if directoryOnTop { + if $0.directory == $1.directory { + // 3. natural fileName + return $0.fileNameView.localizedStandardCompare($1.fileNameView) == ordered + } else { + return $0.directory && !$1.directory + } + } else { + return $0.fileNameView.localizedStandardCompare($1.fileNameView) == ordered + } + } else { + return $0.favorite && !$1.favorite + } + } + return sortedResults + } else { + if directoryOnTop { + results = results.sorted(byKeyPath: layout.sort, ascending: layout.ascending).sorted(byKeyPath: "favorite", ascending: false).sorted(byKeyPath: "directory", ascending: false) + } else { + results = results.sorted(byKeyPath: layout.sort, ascending: layout.ascending).sorted(byKeyPath: "favorite", ascending: false) } } + return Array(results) - // Fetch and detach the corresponding metadatas - return realm.objects(tableMetadata.self) - .filter("ocId IN %@", ocIds) - .map { $0.detachedCopy() } - } ?? [] - - let sorted = await self.sortedMetadata(layoutForView: layoutForView, account: session.account, metadatas: detachedMetadatas) - return sorted + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return [] } - func getRootContainerMetadata(accout: String) -> tableMetadata? { - return performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter("fileName == %@ AND account == %@", NextcloudKit.shared.nkCommonInstance.rootFileName, accout) - .first - .map { $0.detachedCopy() } + func getResultsMetadatas(predicate: NSPredicate, sortedByKeyPath: String, ascending: Bool, arraySlice: Int) -> [tableMetadata] { + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sortedByKeyPath, ascending: ascending).prefix(arraySlice) + return Array(results) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return [] } - func getRootContainerMetadataAsync(accout: String) async -> tableMetadata? { - return await performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter("fileName == %@ AND account == %@", NextcloudKit.shared.nkCommonInstance.rootFileName, accout) - .first - .map { $0.detachedCopy() } + func getResultMetadata(predicate: NSPredicate) -> tableMetadata? { + do { + let realm = try Realm() + return realm.objects(tableMetadata.self).filter(predicate).first + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return nil } - func getMetadatasAsync(predicate: NSPredicate) async -> [tableMetadata] { - await performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .map { $0.detachedCopy() } - } ?? [] + func getResultMetadataFromFileName(_ fileName: String, serverUrl: String, sessionTaskIdentifier: Int) -> tableMetadata? { + do { + let realm = try Realm() + return realm.objects(tableMetadata.self).filter("fileName == %@ AND serverUrl == %@ AND sessionTaskIdentifier == %d", fileName, serverUrl, sessionTaskIdentifier).first + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return nil } - func getTableMetadatasDirectoryFavoriteIdentifierRankAsync(account: String) async -> [String: NSNumber] { - let result = await performRealmReadAsync { realm in - var listIdentifierRank: [String: NSNumber] = [:] - var counter = Int64(10) + func getResultsMetadatasFromGroupfolders(session: NCSession.Session) -> Results? { + var ocId: [String] = [] + let homeServerUrl = utilityFileSystem.getHomeServer(session: session) - let results = realm.objects(tableMetadata.self) - .filter("account == %@ AND directory == true AND favorite == true", account) - .sorted(byKeyPath: "fileNameView", ascending: true) + do { + let realm = try Realm() + let groupfolders = realm.objects(TableGroupfolders.self).filter("account == %@", session.account).sorted(byKeyPath: "mountPoint", ascending: true) - results.forEach { item in - counter += 1 - listIdentifierRank[item.ocId] = NSNumber(value: counter) + for groupfolder in groupfolders { + let mountPoint = groupfolder.mountPoint.hasPrefix("/") ? groupfolder.mountPoint : "/" + groupfolder.mountPoint + let serverUrlFileName = homeServerUrl + mountPoint + + if let directory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", session.account, serverUrlFileName).first, + let result = realm.objects(tableMetadata.self).filter("ocId == %@", directory.ocId).first { + ocId.append(result.ocId) + } } - - return listIdentifierRank + + return realm.objects(tableMetadata.self).filter("ocId IN %@", ocId) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } - return result ?? [:] + + return nil } - func getAssetLocalIdentifiersUploadedAsync() async -> [String]? { - return await performRealmReadAsync { realm in - let results = realm.objects(tableMetadata.self).filter("assetLocalIdentifier != ''") - return results.map { $0.assetLocalIdentifier } + func getTableMetadatasDirectoryFavoriteIdentifierRank(account: String) -> [String: NSNumber] { + var listIdentifierRank: [String: NSNumber] = [:] + var counter = 10 as Int64 + + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter("account == %@ AND directory == true AND favorite == true", account).sorted(byKeyPath: "fileNameView", ascending: true) + for result in results { + counter += 1 + listIdentifierRank[result.ocId] = NSNumber(value: Int64(counter)) + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return listIdentifierRank } @objc func clearMetadatasUpload(account: String) { @@ -1497,104 +1329,59 @@ extension NCManageDatabase { NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - - func getMetadataFromFileId(_ fileId: String?) -> tableMetadata? { - guard let fileId else { - return nil - } - return performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter("fileId == %@", fileId) - .first - .map { $0.detachedCopy() } - } - } + func getAssetLocalIdentifiersUploaded() -> [String]? { + var assetLocalIdentifiers: [String] = [] - /// Asynchronously retrieves a `tableMetadata` object matching the given `fileId`, if available. - /// - Parameter fileId: The file identifier used to query the Realm database. - /// - Returns: A detached copy of the `tableMetadata` object, or `nil` if not found. - func getMetadataFromFileIdAsync(_ fileId: String?) async -> tableMetadata? { - guard let fileId else { - return nil - } - - return await performRealmReadAsync { realm in - let object = realm.objects(tableMetadata.self) - .filter("fileId == %@", fileId) - .first - return object?.detachedCopy() + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter("assetLocalIdentifier != ''") + for result in results { + assetLocalIdentifiers.append(result.assetLocalIdentifier) + } + return assetLocalIdentifiers + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return nil } - /// Asynchronously retrieves and sorts `tableMetadata` objects matching a given predicate and layout. - func getMetadatasAsync(predicate: NSPredicate, - withLayout layoutForView: NCDBLayoutForView?, - withAccount account: String) async -> [tableMetadata] { - let detachedMetadatas = await performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .map { $0.detachedCopy() } - } ?? [] - - let sorted = await self.sortedMetadata(layoutForView: layoutForView, account: account, metadatas: detachedMetadatas) - return sorted - } - - /// Asynchronously retrieves and sorts `tableMetadata` objects matching a given predicate and layout. - func getMetadatasAsyncDataSource(withServerUrl serverUrl: String, - withUserId userId: String, - withAccount account: String, - withLayout layoutForView: NCDBLayoutForView?, - withPreficate predicateSource: NSPredicate? = nil) async -> [tableMetadata] { - var predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName != %@ AND NOT (status IN %@)", account, serverUrl, NextcloudKit.shared.nkCommonInstance.rootFileName, NCGlobal.shared.metadataStatusHideInView) - - if NCPreferences().getPersonalFilesOnly(account: account) { - predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName != %@ AND (ownerId == %@ || ownerId == '') AND mountType == '' AND NOT (status IN %@)", account, serverUrl, NextcloudKit.shared.nkCommonInstance.rootFileName, userId, NCGlobal.shared.metadataStatusHideInView) - } - - if let predicateSource { - predicate = predicateSource + func getMetadataFromDirectory(account: String, serverUrl: String) -> Bool { + do { + let realm = try Realm() + guard let directory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first, + realm.objects(tableMetadata.self).filter("ocId == %@", directory.ocId).first != nil else { return false } + return true + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } - - let detachedMetadatas = await performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .map { $0.detachedCopy() } - } ?? [] - - let cleanedMetadatas = filterAndNormalizeLivePhotos(from: detachedMetadatas) - let sorted = await self.sortedMetadata(layoutForView: layoutForView, account: account, metadatas: cleanedMetadatas) - - return sorted + return false } - func getMetadatasAsync(predicate: NSPredicate, - withSort sortDescriptors: [RealmSwift.SortDescriptor] = [], - withLimit limit: Int? = nil) async -> [tableMetadata]? { - await performRealmReadAsync { realm in - var results = realm.objects(tableMetadata.self) - .filter(predicate) - - if !sortDescriptors.isEmpty { - results = results.sorted(by: sortDescriptors) - } + func getMetadataFromFileId(_ fileId: String?) -> tableMetadata? { + guard let fileId else { return nil } - if let limit { - let sliced = results.prefix(limit) - return sliced.map { $0.detachedCopy() } - } else { - return results.map { $0.detachedCopy() } + do { + let realm = try Realm() + if let result = realm.objects(tableMetadata.self).filter("fileId == %@", fileId).first { + return tableMetadata(value: result) } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return nil } - func hasUploadingMetadataWithChunksOrE2EE() -> Bool { - return performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter("status == %d AND (chunk > 0 OR e2eEncrypted == true)", NCGlobal.shared.metadataStatusUploading) - .first != nil - } ?? false + func getResultMetadataFromOcId(_ ocId: String?) -> tableMetadata? { + guard let ocId else { return nil } + + do { + let realm = try Realm() + return realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return nil } func getMetadataDirectoryAsync(serverUrl: String, account: String) async -> tableMetadata? { @@ -1714,12 +1501,25 @@ extension NCManageDatabase { createMetadata(serverUrl: serverUrl, fileName: fileName, metadata: metadata) } + } + return results + } else { + let results = realm.objects(tableMetadata.self).filter(predicate) + if freeze { + return results.freeze() + } + return results } - completion(metadatas) - } else { - completion(metadatas) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return nil + } + + func getCalculateCumulativeHash(for metadatas: [tableMetadata], account: String, serverUrl: String) -> String { + let concatenatedEtags = metadatas.map { $0.etag }.joined(separator: "-") + return sha256Hash(concatenatedEtags) } func getMediaMetadatas(predicate: NSPredicate) -> ThreadSafeArray? { @@ -1791,4 +1591,16 @@ extension NCManageDatabase { return metadatas } + + func getResultMetadataFromFileId(_ fileId: String?) -> tableMetadata? { + guard let fileId else { return nil } + + do { + let realm = try Realm() + return realm.objects(tableMetadata.self).filter("fileId == %@", fileId).first + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return nil + } } diff --git a/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift b/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift index ec489d893a..c9aae962b1 100644 --- a/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift +++ b/iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift @@ -84,7 +84,7 @@ import XLForm self.navigationItem.rightBarButtonItem = saveButton self.navigationItem.rightBarButtonItem?.isEnabled = false - // title + // title self.title = titleForm fileName = NCUtilityFileSystem().createFileNameDate("Text", ext: getFileExtension()) @@ -113,7 +113,7 @@ import XLForm row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "kNMCFolderCustomCellType", title: "") row.action.formSelector = #selector(changeDestinationFolder(_:)) row.cellConfig["folderImage.image"] = UIImage(named: "folder")!.imageColor(NCBrandColor.shared.customer) - row.cellConfig["photoLabel.textAlignment"] = NSTextAlignment.right.rawValue + row.cellConfig["photoLabel.textAlignment"] = NSTextAlignment.left.rawValue row.cellConfig["photoLabel.font"] = UIFont.systemFont(ofSize: 15.0) row.cellConfig["photoLabel.textColor"] = UIColor.label //photos if(self.fileNameFolder == "/"){ @@ -288,10 +288,14 @@ import XLForm self.present(alert, animated: true) return } + + // Ensure fileName is not nil or empty guard var fileNameForm: String = fileName, !fileNameForm.isEmpty else { return } - // Trim whitespaces after checks above + // Trim whitespaces and newlines fileNameForm = fileNameForm.trimmingCharacters(in: .whitespacesAndNewlines) + + fileName = FileAutoRenamer.rename(fileNameForm, account: session.account) let result = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameForm, mimeType: "", directory: false, account: session.account ) @@ -299,19 +303,25 @@ import XLForm fileNameForm = (fileNameForm as NSString).deletingPathExtension + "." + fileNameExtension } - if NCManageDatabase.shared.getMetadataConflict(account: session.account, serverUrl: serverUrl, fileNameView: String(describing: fileNameForm), nativeFormat: false) != nil { - - let metadataForUpload = NCManageDatabase.shared.createMetadata(fileName: String(describing: fileNameForm), fileNameView: String(describing: fileNameForm), ocId: UUID().uuidString, serverUrl: serverUrl, url: "", contentType: "", session: session, sceneIdentifier: self.appDelegate.sceneIdentifier) - - guard let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict else { return } - - conflict.textLabelDetailNewFile = NSLocalizedString("_now_", comment: "") - conflict.alwaysNewFileNameNumber = true - conflict.serverUrl = serverUrl - conflict.metadatasUploadInConflict = [metadataForUpload] - conflict.delegate = self - - self.present(conflict, animated: true, completion: nil) + // verify if already exists + if NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView == %@", session.account, self.serverUrl, fileNameForm)) != nil { + NCContentPresenter().showError(error: NKError(errorCode: 0, errorDescription: "_rename_already_exists_")) + return +// } +// +// if NCManageDatabase.shared.getMetadataConflict(account: session.account, serverUrl: serverUrl, fileNameView: String(describing: fileNameForm), nativeFormat: false) != nil { +// +// let metadataForUpload = NCManageDatabase.shared.createMetadata(fileName: String(describing: fileNameForm), fileNameView: String(describing: fileNameForm), ocId: UUID().uuidString, serverUrl: serverUrl, url: "", contentType: "", session: session, sceneIdentifier: self.appDelegate.sceneIdentifier) +// +// guard let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict else { return } +// +// conflict.textLabelDetailNewFile = NSLocalizedString("_now_", comment: "") +// conflict.alwaysNewFileNameNumber = true +// conflict.serverUrl = serverUrl +// conflict.metadatasUploadInConflict = [metadataForUpload] +// conflict.delegate = self +// +// self.present(conflict, animated: true, completion: nil) } else { @@ -348,7 +358,7 @@ import XLForm options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) } - NextcloudKit.shared.NCTextCreateFile(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, account: session.account, options: options) { account, url, _, error in + NextcloudKit.shared.textCreateFile(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, account: session.account, options: options) { account, url, _, error in guard error == .success, account == self.session.account, let url = url else { self.navigationItem.rightBarButtonItem?.isEnabled = true NCContentPresenter().showError(error: error) @@ -364,6 +374,7 @@ import XLForm self.dismiss(animated: true, completion: { let metadata = NCManageDatabase.shared.createMetadata(fileName: fileName, fileNameView: fileName, ocId: UUID, serverUrl: self.serverUrl, url: url, contentType: results.mimeType, session: self.session, sceneIdentifier: self.appDelegate.sceneIdentifier) if let viewController = self.appDelegate.activeViewController { +// NCViewer().view(viewController: viewController, metadata: metadata) NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata]) } }) @@ -384,6 +395,7 @@ import XLForm let metadata = NCManageDatabase.shared.createMetadata(fileName: createFileName, fileNameView: createFileName, ocId: UUID, serverUrl: self.serverUrl, url: url, contentType: "", session: self.session, sceneIdentifier: self.appDelegate.sceneIdentifier) AnalyticsHelper.shared.trackCreateFile(metadata: metadata) if let viewController = self.appDelegate.activeViewController { +// NCViewer().view(viewController: viewController, metadata: metadata) NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata]) } }) @@ -412,7 +424,7 @@ import XLForm options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) } - NextcloudKit.shared.NCTextGetListOfTemplates(account: session.account, options: options) { account, templates, _, error in + NextcloudKit.shared.textGetListOfTemplates(account: session.account, options: options) { account, templates, _, error in self.indicator.stopAnimating() diff --git a/iOSClient/NMC Custom Views/FolderPathCustomCell.swift b/iOSClient/NMC Custom Views/FolderPathCustomCell.swift index 901ce667ae..cb7553eda7 100644 --- a/iOSClient/NMC Custom Views/FolderPathCustomCell.swift +++ b/iOSClient/NMC Custom Views/FolderPathCustomCell.swift @@ -7,6 +7,7 @@ // import UIKit +import XLForm class FolderPathCustomCell: XLFormButtonCell{ diff --git a/iOSClient/NMC Custom Views/FolderPathCustomCell.xib b/iOSClient/NMC Custom Views/FolderPathCustomCell.xib index a231ae7c72..caca063abf 100644 --- a/iOSClient/NMC Custom Views/FolderPathCustomCell.xib +++ b/iOSClient/NMC Custom Views/FolderPathCustomCell.xib @@ -1,9 +1,9 @@ - + - + @@ -18,13 +18,13 @@ - + @@ -55,6 +55,7 @@ + @@ -68,7 +69,7 @@ - + diff --git a/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift index 0242fa0f7a..3983e413a6 100644 --- a/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift +++ b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift @@ -7,6 +7,7 @@ // import UIKit +import XLForm class NCCreateDocumentCustomTextField: XLFormBaseCell,UITextFieldDelegate { From 51568e4a4d62d7ec3435d9d5e7c1ff23c35fb4e0 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:23:35 +0530 Subject: [PATCH 009/177] NMC 1990 - Setting and advance setting screen customisation --- .../xcshareddata/xcschemes/Nextcloud.xcscheme | 2 +- .../SettingsTestCases.swift | 341 +++++++++++++ .../AnalysisDataCollectionSwitch.swift | 42 ++ .../Settings/AnalysisDataCollectionSwitch.xib | 73 +++ iOSClient/Settings/CCAdvanced.m | 451 ++++++++++++++++++ iOSClient/Settings/HelpViewController.swift | 57 +++ .../Settings/ImprintViewController.swift | 70 +++ .../Settings/MagentaCloudVersionView.swift | 31 ++ .../Settings/MagentaCloudVersionView.xib | 76 +++ iOSClient/Settings/NCSettings.m | 441 +++++++++++++++++ .../OpenSourceSoftwareViewController.swift | 56 +++ .../PrivacyPolicyViewController.swift | 68 +++ .../PrivacySettingsViewController.swift | 147 ++++++ .../RequiredDataCollectionSwitch.swift | 34 ++ .../Settings/RequiredDataCollectionSwitch.xib | 73 +++ .../SaveSettingsCustomButtonCell.swift | 46 ++ .../Settings/SaveSettingsCustomButtonCell.xib | 46 ++ 17 files changed, 2053 insertions(+), 1 deletion(-) create mode 100644 Tests/NextcloudUnitTests/SettingsTestCases.swift create mode 100644 iOSClient/Settings/AnalysisDataCollectionSwitch.swift create mode 100644 iOSClient/Settings/AnalysisDataCollectionSwitch.xib create mode 100755 iOSClient/Settings/CCAdvanced.m create mode 100644 iOSClient/Settings/HelpViewController.swift create mode 100644 iOSClient/Settings/ImprintViewController.swift create mode 100644 iOSClient/Settings/MagentaCloudVersionView.swift create mode 100644 iOSClient/Settings/MagentaCloudVersionView.xib create mode 100644 iOSClient/Settings/NCSettings.m create mode 100644 iOSClient/Settings/OpenSourceSoftwareViewController.swift create mode 100644 iOSClient/Settings/PrivacyPolicyViewController.swift create mode 100644 iOSClient/Settings/PrivacySettingsViewController.swift create mode 100644 iOSClient/Settings/RequiredDataCollectionSwitch.swift create mode 100644 iOSClient/Settings/RequiredDataCollectionSwitch.xib create mode 100644 iOSClient/Settings/SaveSettingsCustomButtonCell.swift create mode 100644 iOSClient/Settings/SaveSettingsCustomButtonCell.xib diff --git a/Nextcloud.xcodeproj/xcshareddata/xcschemes/Nextcloud.xcscheme b/Nextcloud.xcodeproj/xcshareddata/xcschemes/Nextcloud.xcscheme index 796a251f27..76739e45e5 100755 --- a/Nextcloud.xcodeproj/xcshareddata/xcschemes/Nextcloud.xcscheme +++ b/Nextcloud.xcodeproj/xcshareddata/xcschemes/Nextcloud.xcscheme @@ -116,7 +116,7 @@