diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 4c951b95c0..cef1aef3c5 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -85,6 +85,11 @@ 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 */; }; + B5C9801E2DACEB5A0041B146 /* NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C9801D2DACEB5A0041B146 /* NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift */; }; + B5C980202DAD201A0041B146 /* NCSortMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C9801F2DAD201A0041B146 /* NCSortMenu.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 */; }; F317C82E2E844C5300761AEA /* ClientIntegrationUIViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F317C82D2E844C5300761AEA /* ClientIntegrationUIViewer.swift */; }; @@ -1235,6 +1240,9 @@ 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 = ""; }; + B5C9801D2DACEB5A0041B146 /* NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift"; sourceTree = ""; }; + B5C9801F2DAD201A0041B146 /* NCSortMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCSortMenu.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 = ""; }; @@ -2004,6 +2012,7 @@ children = ( F376A3732E5CC5FF0067EE25 /* ContextMenuActions.swift */, F78C6FDD296D677300C952C3 /* NCContextMenu.swift */, + B5C9801F2DAD201A0041B146 /* NCSortMenu.swift */, 3704EB2923D5A58400455C5B /* NCMenu.storyboard */, 371B5A2D23D0B04500FAFAE9 /* NCMenu.swift */, AF935066276B84E700BD078F /* NCMenu+FloatingPanel.swift */, @@ -2492,6 +2501,7 @@ F7603298252F0E550015A421 /* Collection Common */ = { isa = PBXGroup; children = ( + B5C9801D2DACEB5A0041B146 /* NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift */, F75FE06B2BB01D0D00A0EFEF /* Cell */, F78ACD50219046AC0088454D /* Section Header Footer */, F70D7C3525FFBF81002B9E34 /* NCCollectionViewCommon.swift */, @@ -4512,6 +4522,7 @@ F72944F52A8424F800246839 /* NCEndToEndMetadataV1.swift in Sources */, F710D2022405826100A6033D /* NCViewerContextMenu.swift in Sources */, F765E9CD295C585800A09ED8 /* NCUploadScanDocument.swift in Sources */, + B5C980202DAD201A0041B146 /* NCSortMenu.swift in Sources */, F741C2242B6B9FD600E849BB /* NCMediaSelectTabBar.swift in Sources */, F7BF9D822934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */, AA8D31662D411FA100FE2775 /* NCShareDateCell.swift in Sources */, @@ -4546,6 +4557,7 @@ F70898692EDDB51700EF85BD /* NCSelectOpen+SelectDelegate.swift in Sources */, F7D4BF412CA2E8D800A5E746 /* TOPasscodeViewControllerAnimatedTransitioning.m in Sources */, F7D4BF422CA2E8D800A5E746 /* TOPasscodeSettingsViewController.m in Sources */, + B5C9801E2DACEB5A0041B146 /* NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift in Sources */, F7D4BF432CA2E8D800A5E746 /* TOPasscodeCircleImage.m in Sources */, F78026102E9CFA3700B63436 /* NCTransfersView.swift in Sources */, F7D4BF442CA2E8D800A5E746 /* TOPasscodeView.m in Sources */, diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index 334118babe..f1d8e72990 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -15,6 +15,8 @@ class NCFiles: NCCollectionViewCommon { internal var lastScrollTime: TimeInterval = 0 internal var accumulatedScrollDown: CGFloat = 0 + internal var syncMetadatasTask: Task? + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) @@ -99,6 +101,9 @@ class NCFiles: NCCollectionViewCommon { super.viewWillAppear(animated) Task { + let capabilities = await database.getCapabilities(account: self.session.account) ?? NKCapabilities.Capabilities() + await mainNavigationController?.createPlusMenu(session: self.session, capabilities: capabilities) + await self.reloadDataSource() } } @@ -142,10 +147,53 @@ class NCFiles: NCCollectionViewCommon { // MARK: - DataSource - override func reloadDataSource() async { - guard !isSearchingMode else { - await super.reloadDataSource() - return + override func reloadDataSource() { + guard !isSearchingMode + else { + return super.reloadDataSource() + } + + // Watchdog: this is only a fail safe "dead lock", I don't think the timeout will ever be called but at least nothing gets stuck, if after 5 sec. (which is a long time in this routine), the semaphore is still locked + // + if self.semaphoreReloadDataSource.wait(timeout: .now() + 5) == .timedOut { + self.semaphoreReloadDataSource.signal() + } + + var predicate = self.defaultPredicate + let predicateDirectory = NSPredicate(format: "account == %@ AND serverUrl == %@", session.account, self.serverUrl) + let dataSourceMetadatas = self.dataSource.getMetadatas() + + if NCKeychain().getPersonalFilesOnly(account: session.account) { + predicate = self.personalFilesOnlyPredicate + } + + self.metadataFolder = database.getMetadataFolder(session: session, serverUrl: self.serverUrl) + self.richWorkspaceText = database.getTableDirectory(predicate: predicateDirectory)?.richWorkspace + + let metadatas = self.database.getResultsMetadatasPredicate(predicate, layoutForView: layoutForView) + self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, layoutForView: layoutForView) + + if metadatas.isEmpty { + self.semaphoreReloadDataSource.signal() + return super.reloadDataSource() + } + + self.dataSource.caching(metadatas: metadatas, dataSourceMetadatas: dataSourceMetadatas) { + self.semaphoreReloadDataSource.signal() + super.reloadDataSource() + } + } + + override func getServerData(refresh: Bool = false) async { + await super.getServerData() + + defer { + stopGUIGetServerData() + startSyncMetadata(metadatas: self.dataSource.getMetadatas()) + } + + Task { + await networking.networkingTasks.cancel(identifier: "\(self.serverUrl)_NCFiles") } self.metadataFolder = await self.database.getMetadataFolderAsync(session: self.session, serverUrl: self.serverUrl) @@ -181,7 +229,7 @@ class NCFiles: NCCollectionViewCommon { override func getServerData(forced: Bool = false) async { defer { - stopGUIGetServerData() + restoreDefaultTitle() startSyncMetadata(metadatas: self.dataSource.getMetadatas()) } @@ -240,30 +288,22 @@ class NCFiles: NCCollectionViewCommon { self.collectionView.reloadData() } } - guard resultsReadFile.error == .success, - let metadata = resultsReadFile.metadata else { - return(nil, resultsReadFile.error, reloadRequired) + guard resultsReadFile.error == .success, let metadata = resultsReadFile.metadata else { + return (nil, resultsReadFile.error, false) } - let e2eEncrypted = metadata.e2eEncrypted - let ocId = metadata.ocId await self.database.updateDirectoryRichWorkspaceAsync(metadata.richWorkspace, account: resultsReadFile.account, serverUrl: serverUrl) let tableDirectory = await self.database.getTableDirectoryAsync(ocId: metadata.ocId) - // Verify LivePhoto - // - reloadRequired = await networking.setLivePhoto(account: resultsReadFile.account) - await NCManageDatabase.shared.deleteLivePhotoError() - let shouldSkipUpdate: Bool = ( - !forced && + !refresh && tableDirectory?.etag == metadata.etag && !metadata.e2eEncrypted && !self.dataSource.isEmpty() ) if shouldSkipUpdate { - return (nil, NKError(), reloadRequired) + return (nil, NKError(), false) } startGUIGetServerData() @@ -272,12 +312,75 @@ class NCFiles: NCCollectionViewCommon { let (account, metadataFolder, metadatas, error) = await NCNetworking.shared.readFolderAsync(serverUrl: serverUrl, account: session.account, options: options) { task in - Task { - await NCNetworking.shared.networkingTasks.track(identifier: "\(self.serverUrl)_NCFiles", task: task) - } + self.dataSourceTask = task if self.dataSource.isEmpty() { self.collectionView.reloadData() } + + guard error == .success else { + return (nil, error, false) + } + + if let metadataFolder { + self.metadataFolder = metadataFolder.detachedCopy() + self.richWorkspaceText = metadataFolder.richWorkspace + } + + guard let metadataFolder, + isDirectoryE2EE, + NCKeychain().isEndToEndEnabled(account: account), + await !NCNetworkingE2EE().isInUpload(account: account, serverUrl: serverUrl) else { + return (metadatas, error, true) + } + + /// E2EE + let lock = await self.database.getE2ETokenLockAsync(account: account, serverUrl: serverUrl) + let results = await NCNetworkingE2EE().getMetadata(fileId: metadataFolder.ocId, e2eToken: lock?.e2eToken, account: account) + + let results = await NCNetworkingE2EE().getMetadata(fileId: ocId, e2eToken: lock?.e2eToken, account: account) + + nkLog(tag: self.global.logTagE2EE, message: "Get metadata with error: \(results.error.errorCode)") + nkLog(tag: self.global.logTagE2EE, message: "Get metadata with metadata: \(results.e2eMetadata ?? ""), signature: \(results.signature ?? ""), version \(results.version ?? "")", minimumLogLevel: .verbose) + + guard results.error == .success, + let e2eMetadata = results.e2eMetadata, + let version = results.version else { + + // No metadata fount, re-send it + if results.error.errorCode == NCGlobal.shared.errorResourceNotFound { + await showInfoBanner(controller: self.controller, text: "Metadata not found") + let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, account: account) + if error != .success { + await showErrorBanner(controller: self.controller, text: error.errorDescription) + } + } else { + // show error + await showErrorBanner(controller: self.controller, text: error.errorDescription) + } + + return(metadatas, error, reloadRequired) + } + + let errorDecodeMetadata = await NCEndToEndMetadata().decodeMetadata(e2eMetadata, signature: results.signature, serverUrl: serverUrl, session: self.session) + nkLog(debug: "Decode e2ee metadata with error: \(errorDecodeMetadata.errorCode)") + + if errorDecodeMetadata == .success { + let capabilities = await NKCapabilities.shared.getCapabilities(for: self.session.account) + if version == "v1", NCGlobal.shared.isE2eeVersion2(capabilities.e2EEApiVersion) { + await showInfoBanner(controller: self.controller, text: "Conversion metadata v1 to v2 required, please wait...") + nkLog(tag: self.global.logTagE2EE, message: "Conversion v1 to v2") + NCActivityIndicator.shared.start() + + let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, updateVersionV1V2: true, account: account) + if error != .success { + await showErrorBanner(controller: self.controller, text: error.errorDescription) + } + NCActivityIndicator.shared.stop() + } + } else { + // Client Diagnostic + await self.database.addDiagnosticAsync(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors) + await showErrorBanner(controller: self.controller, text: error.errorDescription) } guard error == .success else { @@ -318,14 +421,14 @@ class NCFiles: NCCollectionViewCommon { // No metadata fount, re-send it if results.error.errorCode == NCGlobal.shared.errorResourceNotFound { - await showInfoBanner(controller: self.controller, text: "Metadata not found") + NCContentPresenter().showInfo(description: "Metadata not found") let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, account: account) if error != .success { - await showErrorBanner(controller: self.controller, text: error.errorDescription) + NCContentPresenter().showError(error: error) } } else { // show error - await showErrorBanner(controller: self.controller, text: error.errorDescription) + NCContentPresenter().showError(error: results.error) } return(metadatas, error, reloadRequired) @@ -336,21 +439,21 @@ class NCFiles: NCCollectionViewCommon { if errorDecodeMetadata == .success { let capabilities = await NKCapabilities.shared.getCapabilities(for: self.session.account) - if version == "v1", NCGlobal.shared.isE2eeVersion2(capabilities.e2EEApiVersion) { - await showInfoBanner(controller: self.controller, text: "Conversion metadata v1 to v2 required, please wait...") + if version == "v1", capabilities.e2EEApiVersion == NCGlobal.shared.e2eeVersionV20 { + NCContentPresenter().showInfo(description: "Conversion metadata v1 to v2 required, please wait...") nkLog(tag: self.global.logTagE2EE, message: "Conversion v1 to v2") NCActivityIndicator.shared.start() let error = await NCNetworkingE2EE().uploadMetadata(serverUrl: serverUrl, updateVersionV1V2: true, account: account) if error != .success { - await showErrorBanner(controller: self.controller, text: error.errorDescription) + NCContentPresenter().showError(error: error) } NCActivityIndicator.shared.stop() } } else { // Client Diagnostic await self.database.addDiagnosticAsync(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors) - await showErrorBanner(controller: self.controller, text: error.errorDescription) + NCContentPresenter().showError(error: error) } return (metadatas, error, reloadRequired) @@ -401,6 +504,12 @@ class NCFiles: NCCollectionViewCommon { navigationController = UIStoryboard(name: "NCIntro", bundle: nil).instantiateInitialViewController() as? UINavigationController } + UIApplication.shared.firstWindow?.rootViewController = navigationController + } else if let account = tblAccount?.account, account != currentAccount { + Task { + await NCAccount().changeAccount(account, userProfile: nil, controller: controller) + } + UIApplication.shared.mainAppWindow?.rootViewController = navigationController } else if let account = tblAccount?.account, account != currentAccount { Task { diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index df72a63889..791088ea2f 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -15,6 +15,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto @IBOutlet weak var imageSelect: UIImageView! @IBOutlet weak var imageStatus: UIImageView! @IBOutlet weak var imageFavorite: UIImageView! +// @IBOutlet weak var imageFavoriteBackground: UIImageView! @IBOutlet weak var imageLocal: UIImageView! @IBOutlet weak var labelTitle: UILabel! @IBOutlet weak var labelInfo: UILabel! @@ -120,6 +121,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto imageItem.layer.masksToBounds = true imageStatus.image = nil imageFavorite.image = nil +// imageFavoriteBackground.isHidden = true imageLocal.image = nil labelTitle.text = "" labelInfo.text = "" @@ -177,9 +179,22 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } func selected(_ status: Bool, isEditMode: Bool) { + // E2EE - remove encrypt folder selection + if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId), metadata.e2eEncrypted { + imageSelect.isHidden = true + } else { + imageSelect.isHidden = isEditMode ? false : true + } +// guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId), !metadata.e2eEncrypted else { +//// guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId), !metadata.e2eEncrypted else { +// backgroundView = nil +// separator.isHidden = false +// imageSelect.isHidden = true +// return +// } if isEditMode { imageItemLeftConstraint.constant = 45 - imageSelect.isHidden = false +// imageSelect.isHidden = false imageShared.isHidden = true imageMore.isHidden = true buttonShared.isHidden = true @@ -187,7 +202,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto accessibilityCustomActions = nil } else { imageItemLeftConstraint.constant = 10 - imageSelect.isHidden = true +// imageSelect.isHidden = true imageShared.isHidden = false imageMore.isHidden = false buttonShared.isHidden = false @@ -204,7 +219,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto backgroundView = blurEffectView separator.isHidden = true } else { - imageSelect.image = NCImageCache.shared.getImageCheckedNo() + imageSelect.image = NCImageCache.shared.getImageCheckedNo() backgroundView = nil separator.isHidden = false } @@ -272,7 +287,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto } } - + override func layoutSubviews() { super.layoutSubviews() // Keep the shadow path in sync with current bounds diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.xib b/iOSClient/Main/Collection Common/Cell/NCListCell.xib index be2b91f06c..21e1dbbccb 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.xib +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.xib @@ -1,77 +1,77 @@ - + - + - + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift index be4595a357..071f9c5eb6 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift @@ -119,6 +119,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { let metadata = self.dataSource.getMetadata(indexPath: indexPath) ?? tableMetadata() let existsImagePreview = utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag, userId: metadata.userId, urlBase: metadata.urlBase) let ext = global.getSizeExtension(column: self.numberOfColumns) + let shares = NCManageDatabase.shared.getTableShares(metadata: metadata) defer { let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() @@ -193,7 +194,8 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.writeInfoDateSize(date: metadata.date, size: metadata.size) } - cell.title?.text = metadata.fileNameView + cell.fileTitleLabel?.text = metadata.fileNameView +// cell.title?.text = metadata.fileNameView // Accessibility [shared] if metadata.ownerId != appDelegate.userId, appDelegate.account == metadata.account { if metadata.ownerId != metadata.userId { @@ -204,9 +206,9 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { let tblDirectory = database.getTableDirectory(ocId: metadata.ocId) if metadata.e2eEncrypted { - cell.previewImageView?.image = imageCache.getFolderEncrypted(account: metadata.account) + cell.filePreviewImageView?.image = imageCache.getFolderEncrypted() } else if isShare { - cell.previewImageView?.image = imageCache.getFolderSharedWithMe(account: metadata.account) + cell.filePreviewImageView?.image = imageCache.getFolderSharedWithMe() } else if !metadata.shareType.isEmpty { metadata.shareType.contains(NKShare.ShareType.publicLink.rawValue) ? (cell.previewImageView?.image = imageCache.getFolderPublic(account: metadata.account)) : @@ -317,7 +319,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.favoriteImageView?.image = imageCache.getImageFavorite() a11yValues.append(NSLocalizedString("_favorite_short_", comment: "")) } - + // Share image if isShare { cell.shareImageView?.image = imageCache.getImageShared() @@ -326,7 +328,19 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { (cell.shareImageView?.image = imageCache.getImageShareByLink()) : (cell.shareImageView?.image = imageCache.getImageShared()) } else { - cell.shareImageView?.image = imageCache.getImageCanShare() + cell.fileSharedImage?.image = NCImageCache.shared.getImageCanShare().image(color: NCBrandColor.shared.gray60) + } + if session.account != metadata.account { + cell.fileSharedImage?.image = imageCache.getImageShared() + } + if (!metadata.shareType.isEmpty || !(shares.share?.isEmpty ?? true) || (shares.firstShareLink != nil)){ + cell.fileSharedImage?.image = cell.fileSharedImage?.image?.image(color: NCBrandColor.shared.customer) + } else { + cell.fileSharedImage?.image = NCImageCache.shared.getImageCanShare().image(color: NCBrandColor.shared.gray60) + } + + if metadata.permissions.contains("S"), (metadata.permissions.range(of: "S") != nil) { + cell.fileSharedImage?.image = NCImageCache.shared.getImageSharedWithMe() } // Button More @@ -337,14 +351,14 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.setButtonMore(image: imageCache.getImageButtonMore()) } - // Status + // Staus if metadata.isLivePhoto { +// cell.fileStatusImage?.image = utility.loadImage(named: "livephoto", colors: isLayoutPhoto ? [.white] : [NCBrandColor.shared.iconImageColor2]) cell.statusImageView?.image = utility.loadImage(named: "livephoto", colors: [NCBrandColor.shared.iconImageColor]) a11yValues.append(NSLocalizedString("_upload_mov_livephoto_", comment: "")) } else if metadata.isVideo { cell.statusImageView?.image = utility.loadImage(named: "play.circle.fill", colors: [.systemBackgroundInverted, .systemGray5]) } - switch metadata.status { case global.metadataStatusWaitCreateFolder: cell.statusImageView?.image = utility.loadImage(named: "arrow.triangle.2.circlepath", colors: NCBrandColor.shared.iconImageMultiColors) @@ -371,31 +385,6 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { break } - // AVATAR - if !metadata.ownerId.isEmpty, metadata.ownerId != metadata.userId { - let fileName = NCSession.shared.getFileName(urlBase: metadata.urlBase, user: metadata.ownerId) - if let image = NCImageCache.shared.getImageCache(key: fileName) { - cell.avatarImageView?.contentMode = .scaleAspectFill - cell.avatarImageView?.image = image - } else { - self.database.getImageAvatarLoaded(fileName: fileName) { image, tblAvatar in - if let image { - cell.avatarImageView?.contentMode = .scaleAspectFill - cell.avatarImageView?.image = image - NCImageCache.shared.addImageCache(image: image, key: fileName) - } else { - cell.avatarImageView?.contentMode = .scaleAspectFill - cell.avatarImageView?.image = self.utility.loadUserImage(for: metadata.ownerId, displayName: metadata.ownerDisplayName, urlBase: metadata.urlBase) - } - - if !(tblAvatar?.loaded ?? false), - self.networking.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty { - self.networking.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: metadata.ownerId, fileName: fileName, account: metadata.account, view: collectionView)) - } - } - } - } - // URL if metadata.classFile == NKTypeClassFile.url.rawValue { cell.localImageView?.image = nil @@ -469,7 +458,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.hideButtonMore(true) } - cell.setIconOutlines() +// cell.setIconOutlines() // Obligatory here, at the end !! cell.metadata = metadata diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift index 8b18f1e11e..76b358def0 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift @@ -131,15 +131,16 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { } if self.isEditMode { - if let index = self.fileSelect.firstIndex(of: metadata.ocId) { - self.fileSelect.remove(at: index) - } else { - self.fileSelect.append(metadata.ocId) + if !metadata.e2eEncrypted { + if let index = self.fileSelect.firstIndex(of: metadata.ocId) { + self.fileSelect.remove(at: index) + } else { + self.fileSelect.append(metadata.ocId) + } + self.collectionView.reloadItems(at: [indexPath]) + self.tabBarSelect?.update(fileSelect: self.fileSelect, metadatas: self.getSelectedMetadatas(), userId: metadata.userId) + // self.collectionView.reloadSections(IndexSet(integer: indexPath.section)) } - self.collectionView.reloadItems(at: [indexPath]) - self.tabBarSelect?.update(fileSelect: self.fileSelect, metadatas: self.getSelectedMetadatas(), userId: metadata.userId) - // self.collectionView.reloadSections(IndexSet(integer: indexPath.section)) - self.collectionView.collectionViewLayout.invalidateLayout() return } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift index 76a5b60c0f..c07c681344 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBarDelegate.swift @@ -1,20 +1,39 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCCollectionViewCommon+SelectTabBar.swift +// Nextcloud +// +// Created by Milen on 01.03.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 import NextcloudKit -extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { +extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate, NCSelectableNavigationView { + func selectAll() { - if !fileSelect.isEmpty, self.dataSource.getMetadatas().count == fileSelect.count { - fileSelect = [] - } else { - fileSelect = self.dataSource.getMetadatas().compactMap({ $0.ocId }) - } + fileSelect = selectableDataSource.compactMap({ $0.primaryKeyValue }) tabBarSelect?.update(fileSelect: fileSelect, metadatas: getSelectedMetadatas(), userId: session.userId) - self.collectionView.reloadData() + DispatchQueue.main.async { + self.collectionView.reloadData() + self.setNavigationRightItems(enableMenu: false) + } } func delete() { @@ -34,13 +53,17 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { }) } - alertController.addAction(UIAlertAction(title: NSLocalizedString("_remove_local_file_", comment: ""), style: .default) { (_: UIAlertAction) in + alertController.addAction(UIAlertAction(title: NSLocalizedString("_remove_local_file_", comment: ""), style: .default) { [self] (_: UIAlertAction) in let copyMetadatas = metadatas Task { var error = NKError() + var ocId: [String] = [] for metadata in copyMetadatas where error == .success { - error = await self.networking.deleteCache(metadata, sceneIdentifier: self.controller?.sceneIdentifier) + error = await NCNetworking.shared.deleteCache(metadata, sceneIdentifier: self.controller?.sceneIdentifier) + if error == .success { + ocId.append(metadata.ocId) + } } await self.setEditMode(false) } @@ -85,7 +108,6 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { } await self.setEditMode(false) } - })) alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel)) self.present(alert, animated: true) @@ -131,10 +153,212 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { if editMode { navigationItem.leftBarButtonItems = nil } else { - await (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() + ///Magentacloud branding changes hide user account button on left navigation bar + setNavigationLeftItems() + } + + navigationController?.interactivePopGestureRecognizer?.isEnabled = !editMode + navigationItem.hidesBackButton = editMode + searchController(enabled: !editMode) + self.setNavigationRightItems(enableMenu: true) + self.collectionView.reloadData() + } + + func convertLivePhoto(metadataFirst: tableMetadata?, metadataLast: tableMetadata?) { + if let metadataFirst, let metadataLast { + Task { + let userInfo: [String: Any] = ["serverUrl": metadataFirst.serverUrl, + "account": metadataFirst.account] + + await NCNetworking.shared.setLivePhoto(metadataFirst: metadataFirst, metadataLast: metadataLast, userInfo: userInfo) + } + } + setEditMode(false) + } + + /// If explicit `isOn` is not set, it will invert `isEditMode` + func toggleSelect(isOn: Bool? = nil) { + DispatchQueue.main.async { + self.isEditMode = isOn ?? !self.isEditMode + self.setEditMode(self.isEditMode) } await (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() self.collectionView.reloadData() } + + func createMenuActions() -> [NCMenuAction] { + var actions = [NCMenuAction]() + + actions.append(.cancelAction { + self.toggleSelect() + }) + if fileSelect.count != selectableDataSource.count { + actions.append(.selectAllAction(action: selectAll)) + } + + guard !fileSelect.isEmpty else { return actions } + + actions.append(.seperator(order: 0)) + + var selectedMetadatas: [tableMetadata] = [] + var selectedMediaMetadatas: [tableMetadata] = [] + var isAnyOffline = false + var isAnyFolder = false + var isAnyLocked = false + var canUnlock = true + var canOpenIn = false + var isDirectoryE2EE = false + + for ocId in fileSelect { + guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { continue } + if metadata.e2eEncrypted { + fileSelect.removeAll(where: {$0 == metadata.ocId}) + } else { + selectedMetadatas.append(metadata) + } + + if [NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue].contains(metadata.classFile) { + selectedMediaMetadatas.append(metadata) + } + if metadata.directory { isAnyFolder = true } + if metadata.lock { + isAnyLocked = true + if metadata.lockOwner != session.userId { + canUnlock = false + } + } + + guard !isAnyOffline else { continue } + if metadata.directory, + let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", session.account, metadata.serverUrl + "/" + metadata.fileName)) { + isAnyOffline = directory.offline + } else if let localFile = NCManageDatabase.shared.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) { + isAnyOffline = localFile.offline + } // else: file is not offline, continue + + if !metadata.directory { + canOpenIn = true + } + + if metadata.isDirectoryE2EE { + isDirectoryE2EE = true + } + } + + if canOpenIn { + actions.append(.share(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: { self.toggleSelect() })) + } + + if !isAnyFolder, canUnlock, !NCCapabilities.shared.getCapabilities(account: controller?.account).capabilityFilesLockVersion.isEmpty { + actions.append(.lockUnlockFiles(shouldLock: !isAnyLocked, metadatas: selectedMetadatas, completion: { self.toggleSelect() })) + } + + if !selectedMediaMetadatas.isEmpty { +// var title: String = NSLocalizedString("_save_selected_files_", comment: "") +// var icon = NCUtility().loadImage(named: "save_files",colors: [NCBrandColor.shared.iconImageColor]) +// if selectedMediaMetadatas.allSatisfy({ NCManageDatabase.shared.getMetadataLivePhoto(metadata: $0) != nil }) { +// title = NSLocalizedString("_livephoto_save_", comment: "") +// icon = NCUtility().loadImage(named: "livephoto") +// } +// +// actions.append(NCMenuAction( +// title: title, +// icon: icon, +// order: 0, +// action: { _ in +// for metadata in selectedMediaMetadatas { +// if let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) { +// NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV, hudView: self.view)) +// } else { +// if NCUtilityFileSystem().fileProviderStorageExists(metadata) { +// NCActionCenter.shared.saveAlbum(metadata: metadata, controller: self.tabBarController as? NCMainTabBarController) +// } else { +// if NCNetworking.shared.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty { +// NCNetworking.shared.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum)) +// } +// } +// } +// } +// self.toggleSelect() +// } +// ) +// ) + actions.append(.saveMediaAction(selectedMediaMetadatas: selectedMediaMetadatas, controller: self.controller, completion: { self.toggleSelect() })) + } + actions.append(.setAvailableOfflineAction(selectedMetadatas: selectedMetadatas, isAnyOffline: isAnyOffline, viewController: self, completion: { + self.reloadDataSource() + self.toggleSelect() + })) + + if !isDirectoryE2EE { + actions.append(.moveOrCopyAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: { self.toggleSelect() })) + actions.append(.copyAction(fileSelect: fileSelect, controller: self.controller, completion: { self.toggleSelect() })) + } + actions.append(.deleteAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: { self.toggleSelect() })) + return actions + } + + func setNavigationRightItems(enableMenu: Bool = false) { + DispatchQueue.main.async { + if self.isEditMode { + let more = UIBarButtonItem(image: UIImage(systemName: "ellipsis"), style: .plain) { self.presentMenu(with: self.createMenuActions())} + self.navigationItem.rightBarButtonItems = [more] + } else { + let select = UIBarButtonItem(title: NSLocalizedString("_select_", comment: ""), style: UIBarButtonItem.Style.plain) { self.toggleSelect() } + let notification = UIBarButtonItem(image: UIImage(systemName: "bell"), style: .plain, action: self.tapNotification) + if self.layoutKey == NCGlobal.shared.layoutViewFiles { + self.navigationItem.rightBarButtonItems = [select, notification] + } else { + self.navigationItem.rightBarButtonItems = [select] + } + let transfer = UIBarButtonItem(image: UIImage(systemName: "arrow.left.arrow.right.circle.fill"), style: .plain, action: self.tapTransfer) + let resultsCount = self.database.getResultsMetadatas(predicate: NSPredicate(format: "status != %i", NCGlobal.shared.metadataStatusNormal))?.count ?? 0 + + if self.layoutKey == NCGlobal.shared.layoutViewFiles { + self.navigationItem.rightBarButtonItems = resultsCount > 0 ? [select, notification, transfer] : [select, notification] + } else { + self.navigationItem.rightBarButtonItems = [select] + } + } + guard self.layoutKey == NCGlobal.shared.layoutViewFiles else { return } + self.navigationItem.title = self.titleCurrentFolder + } + } + + func onListSelected() { + if layoutForView?.layout == NCGlobal.shared.layoutGrid { + headerMenu?.buttonSwitch.accessibilityLabel = NSLocalizedString("_grid_view_", comment: "") + layoutForView?.layout = NCGlobal.shared.layoutList + NCManageDatabase.shared.setLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl, layout: layoutForView?.layout) + self.groupByField = "name" + if self.dataSource.groupByField != self.groupByField { + self.dataSource.changeGroupByField(self.groupByField) + } + self.saveLayout(layoutForView!) + self.collectionView.reloadData() + self.collectionView.collectionViewLayout.invalidateLayout() + self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) {_ in self.isTransitioning = false } + } + } + + func onGridSelected() { + if layoutForView?.layout == NCGlobal.shared.layoutList { + headerMenu?.buttonSwitch.accessibilityLabel = NSLocalizedString("_list_view_", comment: "") + layoutForView?.layout = NCGlobal.shared.layoutGrid + NCManageDatabase.shared.setLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl, layout: layoutForView?.layout) + if isSearchingMode { + self.groupByField = "name" + } else { + self.groupByField = "classFile" + } + if self.dataSource.groupByField != self.groupByField { + self.dataSource.changeGroupByField(self.groupByField) + } + self.saveLayout(layoutForView!) + self.collectionView.reloadData() + self.collectionView.collectionViewLayout.invalidateLayout() + self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) {_ in self.isTransitioning = false } + } + } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift new file mode 100644 index 0000000000..8a08faca4e --- /dev/null +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift @@ -0,0 +1,94 @@ +// +// NCCollectionViewCommon+SwipeCollectionViewCellDelegate.swift +// Nextcloud +// +// Created by Milen on 01.03.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 Foundation +import SwipeCellKit + +extension NCCollectionViewCommon: SwipeCollectionViewCellDelegate { + func collectionView(_ collectionView: UICollectionView, editActionsForItemAt indexPath: IndexPath, for orientation: SwipeCellKit.SwipeActionsOrientation) -> [SwipeCellKit.SwipeAction]? { + guard orientation == .right, let metadata = self.dataSource.cellForItemAt(indexPath: indexPath) else { return nil } + + let scaleTransition = ScaleTransition(duration: 0.3, initialScale: 0.8, threshold: 0.8) + + // wait a fix for truncate the text .. ? .. + let favoriteAction = SwipeAction(style: .default, title: NSLocalizedString(metadata.favorite ? "_favorite_short_" : "_favorite_short_", comment: "") ) { _, _ in + NCNetworking.shared.favoriteMetadata(metadata) { error in + if error != .success { + NCContentPresenter().showError(error: error) + } + } + } + favoriteAction.backgroundColor = NCBrandColor.shared.yellowFavorite + favoriteAction.image = .init(systemName: "star.fill") + favoriteAction.transitionDelegate = scaleTransition + favoriteAction.hidesWhenSelected = true + + var actions = [favoriteAction] + + let shareAction = SwipeAction(style: .default, title: NSLocalizedString("_share_", comment: "")) { _, _ in + NCActionCenter.shared.openActivityViewController(selectedMetadata: [metadata], controller: self.controller) + } + shareAction.backgroundColor = .blue + shareAction.image = .init(systemName: "square.and.arrow.up") + shareAction.transitionDelegate = scaleTransition + shareAction.hidesWhenSelected = true + + let deleteAction = SwipeAction(style: .destructive, title: NSLocalizedString("_delete_", comment: "")) { _, _ in + let titleDelete: String + + if metadata.directory { + titleDelete = NSLocalizedString("_delete_folder_", comment: "") + } else { + titleDelete = NSLocalizedString("_delete_file_", comment: "") + } + + let message = NSLocalizedString("_want_delete_", comment: "") + "\n - " + metadata.fileNameView + + let alertController = UIAlertController.deleteFileOrFolder(titleString: titleDelete + "?", message: message, canDeleteServer: !metadata.lock, selectedMetadatas: [metadata], sceneIdentifier: self.controller?.sceneIdentifier) { _ in } + + self.viewController.present(alertController, animated: true, completion: nil) + } + deleteAction.image = UIImage.init(systemName: "trash") + deleteAction.style = .destructive + deleteAction.transitionDelegate = scaleTransition + deleteAction.hidesWhenSelected = true + + if !NCManageDatabase.shared.isMetadataShareOrMounted(metadata: metadata, metadataFolder: metadataFolder) { + actions.insert(deleteAction, at: 0) + } + + if metadata.canShare { + actions.append(shareAction) + } + + return actions + } + + func collectionView(_ collectionView: UICollectionView, editActionsOptionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions { + var options = SwipeOptions() + options.expansionStyle = .selection + options.transitionStyle = .border + options.backgroundColor = .clear + return options + } +} diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 37b71d071c..67d2078f37 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -4,10 +4,12 @@ import UIKit import SwiftUI +import Realm import RealmSwift import NextcloudKit import EasyTipView import LucidBanner +import MoEngageInApps class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { @@ -53,6 +55,14 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, var tipViewAccounts: EasyTipView? var syncMetadatasTask: Task? + var tipViewAutoUpload: EasyTipView? + var headerMenu: NCSectionHeaderMenu? + var headerMenuTransferView = false + var headerMenuButtonsView: Bool = true + var headerRichWorkspaceDisable: Bool = false + + var selectableDataSource: [RealmSwiftObject] { dataSource.getMetadataSourceForAllSections() } + // DECLARE var layoutKey = "" var titleCurrentFolder = "" @@ -146,9 +156,8 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, return pinchGesture.state == .began || pinchGesture.state == .changed } - func isRecommendationActived() async -> Bool { - let capabilities = await NKCapabilities.shared.getCapabilities(for: session.account) - return self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session) && capabilities.recommendations + var capabilities: NKCapabilities.Capabilities { + NKCapabilities.shared.getCapabilitiesBlocking(for: session.account) } internal let debouncerReloadDataSource = NCDebouncer(maxEventCount: NCBrandOptions.shared.numMaximumProcess) @@ -230,6 +239,14 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, let dropInteraction = UIDropInteraction(delegate: self) self.navigationController?.navigationItem.leftBarButtonItems?.first?.customView?.addInteraction(dropInteraction) + if(!UserDefaults.standard.bool(forKey: "isInitialPrivacySettingsShowed") || isApplicationUpdated()){ + redirectToPrivacyViewController() + + //set current app version + let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String + UserDefaults.standard.set(appVersion, forKey: "CurrentAppVersion") + } + registerForTraitChanges([UITraitUserInterfaceStyle.self]) { [weak self] (view: NCCollectionViewCommon, _) in guard let self else { return } @@ -250,6 +267,8 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, await self.debouncerReloadData.resume() } } + + NotificationCenter.default.addObserver(self, selector: #selector(updateIcons), name: NSNotification.Name(rawValue: global.notificationCenterUpdateIcons), object: nil) DispatchQueue.main.async { self.collectionView?.collectionViewLayout.invalidateLayout() @@ -351,6 +370,26 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, override var canBecomeFirstResponder: Bool { return true } + + @objc func updateIcons() { + collectionView.reloadData() + } + + func isApplicationUpdated() -> Bool { + let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String ?? "" + let currentVersion = UserDefaults.standard.string(forKey: "CurrentAppVersion") + return currentVersion != appVersion + } + + func redirectToPrivacyViewController() { + let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) + let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as? UINavigationController + newViewController?.modalPresentationStyle = .fullScreen + self.present(newViewController!, animated: true, completion: nil) + } + + tabBarSelect?.setFrame() + } // MARK: - NotificationCenter @@ -428,6 +467,12 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, return NCBrandOptions.shared.brand } + func accountSettingsDidDismiss(tblAccount: tableAccount?, controller: NCMainTabBarController?) { } + + func resetPlusButtonAlpha(animated: Bool = true) { } + + func isHiddenPlusButton(_ isHidden: Bool) { } + @MainActor func startGUIGetServerData() { self.dataSource.setGetServerData(false) @@ -461,6 +506,57 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, self.navigationItem.title = self.titleCurrentFolder } + // MARK: - Empty + + func emptyDataSetView(_ view: NCEmptyView) { + + self.emptyDataSet?.setOffset(getHeaderHeight()) + if isSearchingMode { + view.emptyImage.image = UIImage(named: "search")?.image(color: .gray, size: UIScreen.main.bounds.width) + if self.dataSourceTask?.state == .running { + view.emptyTitle.text = NSLocalizedString("_search_in_progress_", comment: "") + } else { + view.emptyTitle.text = NSLocalizedString("_search_no_record_found_", comment: "") + } + view.emptyDescription.text = NSLocalizedString("_search_instruction_", comment: "") + } else if self.dataSourceTask?.state == .running { + view.emptyImage.image = UIImage(named: "networkInProgress")?.image(color: .gray, size: UIScreen.main.bounds.width) + view.emptyTitle.text = NSLocalizedString("_request_in_progress_", comment: "") + view.emptyDescription.text = "" + } else { + if serverUrl.isEmpty { + view.emptyImage.image = emptyImage + view.emptyTitle.text = NSLocalizedString(emptyTitle, comment: "") + view.emptyDescription.text = NSLocalizedString(emptyDescription, comment: "") + } else { + view.emptyImage.image = UIImage(named: "folder_nmcloud") + view.emptyTitle.text = NSLocalizedString("_files_no_files_", comment: "") + view.emptyDescription.text = NSLocalizedString("_no_file_pull_down_", comment: "") + } + } + + let spinner = UIActivityIndicatorView(style: .medium) + spinner.startAnimating() + + let container = UIView() + container.translatesAutoresizingMaskIntoConstraints = false + container.addSubview(spinner) + + spinner.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + spinner.centerXAnchor.constraint(equalTo: container.centerXAnchor), + spinner.centerYAnchor.constraint(equalTo: container.centerYAnchor) + ]) + + self.navigationItem.titleView = container + } + + @MainActor + func restoreDefaultTitle() { + self.navigationItem.titleView = nil + self.navigationItem.title = self.titleCurrentFolder + } + // MARK: - SEARCH func searchController(enabled: Bool) { @@ -514,6 +610,63 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, // MARK: - TAP EVENT + func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + } + + func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + } + + func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) { + guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } + + NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) +// NCDownloadAction.shared.openShare(viewController: self, metadata: metadata, page: .sharing) + TealiumHelper.shared.trackEvent(title: "magentacloud-app.filebrowser.sharing", data: ["": ""]) + appDelegate.adjust.trackEvent(TriggerEvent(Sharing.rawValue)) + } + + func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } + toggleMenu(metadata: metadata, image: image, sender: sender) + } + + func tapRichWorkspace(_ sender: Any) { + if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { + if let viewerRichWorkspace = navigationController.topViewController as? NCViewerRichWorkspace { + viewerRichWorkspace.richWorkspaceText = richWorkspaceText ?? "" + viewerRichWorkspace.serverUrl = serverUrl + viewerRichWorkspace.delegate = self + + navigationController.modalPresentationStyle = .fullScreen + self.present(navigationController, animated: true, completion: nil) + } + } + } + + func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { + toggleMenu(metadata: metadata, image: image, sender: sender) + } + + func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { + unifiedSearchMore(metadataForSection: metadataForSection) + } + + func tapRecommendations(with metadata: tableMetadata) { + didSelectMetadata(metadata, withOcIds: false) + } + + func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressMoreGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) } diff --git a/iOSClient/Menu/NCMenuAction.swift b/iOSClient/Menu/NCMenuAction.swift index 8d8c1d11c9..cfd8feca2d 100644 --- a/iOSClient/Menu/NCMenuAction.swift +++ b/iOSClient/Menu/NCMenuAction.swift @@ -110,7 +110,7 @@ extension NCMenuAction { static func deleteOrUnshareAction(selectedMetadatas: [tableMetadata], metadataFolder: tableMetadata? = nil, controller: NCMainTabBarController?, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { var titleDelete = NSLocalizedString("_delete_", comment: "") var message = NSLocalizedString("_want_delete_", comment: "") - var icon = "trash" + var icon = "trashIcon" var destructive = false var color = NCBrandColor.shared.iconImageColor @@ -166,8 +166,10 @@ extension NCMenuAction { /// Open "share view" (activity VC) to open files in another app static func share(selectedMetadatas: [tableMetadata], controller: NCMainTabBarController?, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { NCMenuAction( - title: NSLocalizedString("_share_", comment: ""), - icon: NCUtility().loadImage(named: "square.and.arrow.up", colors: [NCBrandColor.shared.iconImageColor]), +// title: NSLocalizedString("_share_", comment: ""), +// icon: NCUtility().loadImage(named: "share", colors: [NCBrandColor.shared.iconImageColor]), + title: NSLocalizedString("_open_in_", comment: ""), + icon: NCUtility().loadImage(named: "open_file",colors: [NCBrandColor.shared.iconImageColor]), order: order, sender: sender, action: { _ in @@ -183,7 +185,7 @@ extension NCMenuAction { static func setAvailableOfflineAction(selectedMetadatas: [tableMetadata], isAnyOffline: Bool, viewController: UIViewController, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { NCMenuAction( title: isAnyOffline ? NSLocalizedString("_remove_available_offline_", comment: "") : NSLocalizedString("_set_available_offline_", comment: ""), - icon: NCUtility().loadImage(named: "icloud.and.arrow.down", colors: [NCBrandColor.shared.iconImageColor]), + icon: NCUtility().loadImage(named: "cloudDownload", colors: [NCBrandColor.shared.iconImageColor]), order: order, sender: sender, action: { _ in @@ -215,11 +217,26 @@ extension NCMenuAction { } ) } + + /// Copy files to pasteboard + static func copyAction(fileSelect: [String], controller: NCMainTabBarController?, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { + NCMenuAction( + title: NSLocalizedString("_copy_file_", comment: ""), + icon: NCUtility().loadImage(named: "copy", colors: [NCBrandColor.shared.iconImageColor]), + order: order, + sender: sender, + action: { _ in + NCDownloadAction.shared.copyPasteboard(pasteboardOcIds: fileSelect, controller: controller) + completion?() + } + ) + } + /// Open view that lets the user move or copy the files within Nextcloud static func moveOrCopyAction(selectedMetadatas: [tableMetadata], account: String, viewController: UIViewController, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { NCMenuAction( title: NSLocalizedString("_move_or_copy_", comment: ""), - icon: NCUtility().loadImage(named: "rectangle.portrait.and.arrow.right", colors: [NCBrandColor.shared.iconImageColor]), + icon: NCUtility().loadImage(named: "move", colors: [NCBrandColor.shared.iconImageColor]), order: order, sender: sender, action: { _ in @@ -272,4 +289,107 @@ extension NCMenuAction { } ) } + + /// Open "share view" (activity VC) to open files in another app + static func openInAction(selectedMetadatas: [tableMetadata], controller: NCMainTabBarController?, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { + NCMenuAction( + title: NSLocalizedString("_open_in_", comment: ""), + icon: NCUtility().loadImage(named: "open_file",colors: [NCBrandColor.shared.iconImageColor]), + order: order, + sender: sender, + action: { _ in + NCDownloadAction.shared.openActivityViewController(selectedMetadata: selectedMetadatas, controller: controller, sender: sender) + completion?() + } + ) + } + + /// Save selected files to user's photo library + static func saveMediaAction(selectedMediaMetadatas: [tableMetadata], controller: NCMainTabBarController?, order: Int = 0, sender: Any?, completion: (() -> Void)? = nil) -> NCMenuAction { + var title: String = NSLocalizedString("_save_selected_files_", comment: "") + var icon = NCUtility().loadImage(named: "save_files",colors: [NCBrandColor.shared.iconImageColor]) + if selectedMediaMetadatas.allSatisfy({ NCManageDatabase.shared.getMetadataLivePhoto(metadata: $0) != nil }) { + title = NSLocalizedString("_livephoto_save_", comment: "") + icon = NCUtility().loadImage(named: "livephoto") + } + + return NCMenuAction( + title: title, + icon: icon, + order: order, + sender: sender, + action: { _ in + for metadata in selectedMediaMetadatas { + if let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) { + NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV, hudView: controller?.view ?? UIView())) + } else { + if NCUtilityFileSystem().fileProviderStorageExists(metadata) { + NCDownloadAction.shared.saveAlbum(metadata: metadata, controller: controller) + } else { + if NCNetworking.shared.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty { + NCNetworking.shared.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum)) + } + } + } + } + completion?() + } + ) + } + + /// Open AirPrint view to print a single file + static func printAction(metadata: tableMetadata, order: Int = 0, sender: Any?) -> NCMenuAction { + NCMenuAction( + title: NSLocalizedString("_print_", comment: ""), + icon: NCUtility().loadImage(named: "printer", colors: [NCBrandColor.shared.iconImageColor]), + order: order, + sender: sender, + action: { _ in + if NCUtilityFileSystem().fileProviderStorageExists(metadata) { + metadata.sessionSelector = NCGlobal.shared.selectorPrint + NCDownloadAction.shared.downloadedFile(metadata: metadata, error: NKError()) + } else { + NCNetworking.shared.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorPrint)) + } + } + ) + } + + // MARK: - Print + + static func printDocument(metadata: tableMetadata) { + +// let fileNameURL = URL(fileURLWithPath: NCUtilityFileSystem().getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)) +// let printController = UIPrintInteractionController.shared +// let printInfo = UIPrintInfo(dictionary: nil) +// +// printInfo.jobName = fileNameURL.lastPathComponent +// printInfo.outputType = metadata.isImage ? .photo : .general +// printController.printInfo = printInfo +// printController.showsNumberOfCopies = true +// +// guard !UIPrintInteractionController.canPrint(fileNameURL) else { +// printController.printingItem = fileNameURL +// printController.present(animated: true) +// return +// } +// +// // can't print without data +// guard let data = try? Data(contentsOf: fileNameURL) else { return } +// +// if let svg = SVGKImage(data: data) { +// printController.printingItem = svg.uiImage +// printController.present(animated: true) +// return +// } +// +// guard let text = String(data: data, encoding: .utf8) else { return } +// let formatter = UISimpleTextPrintFormatter(text: text) +// formatter.perPageContentInsets.top = 72 +// formatter.perPageContentInsets.bottom = 72 +// formatter.perPageContentInsets.left = 72 +// formatter.perPageContentInsets.right = 72 +// printController.printFormatter = formatter +// printController.present(animated: true) + } } diff --git a/iOSClient/Menu/NCSortMenu.swift b/iOSClient/Menu/NCSortMenu.swift new file mode 100644 index 0000000000..ffb6f0b1d7 --- /dev/null +++ b/iOSClient/Menu/NCSortMenu.swift @@ -0,0 +1,171 @@ +// +// NCSortMenu.swift +// Nextcloud +// +// Created by Marino Faggiana on 27/08/2020. +// Copyright © 2020 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 FloatingPanel +import NextcloudKit + +class NCSortMenu: NSObject { + + private var sortButton: UIButton? + private var serverUrl: String = "" + private var hideDirectoryOnTop: Bool? + private var account: String = "" + + private var key = "" + + func toggleMenu(viewController: UIViewController, account: String, key: String, sortButton: UIButton?, serverUrl: String, hideDirectoryOnTop: Bool = false) { + + self.key = key + self.sortButton = sortButton + self.serverUrl = serverUrl + self.hideDirectoryOnTop = hideDirectoryOnTop + self.account = account + +// guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: account, key: key, serverUrl: serverUrl) else { return } +// var actions = [NCMenuAction]() +// var title = "" +// var icon = UIImage() +// +// if layoutForView.ascending { +// title = NSLocalizedString("_order_by_name_z_a_", comment: "") +// icon = UIImage(named: "sortFileNameZA")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } else { +// title = NSLocalizedString("_order_by_name_a_z_", comment: "") +// icon = UIImage(named: "sortFileNameAZ")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// icon = UIImage(named: "sortFileNameZA")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } else { +// title = NSLocalizedString("_order_by_name_a_z_", comment: "") +// icon = UIImage(named: "sortFileNameAZ")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } +// +// actions.append( +// NCMenuAction( +// title: title, +// icon: icon, +// selected: layoutForView.sort == "fileName", +// on: layoutForView.sort == "fileName", +// action: { _ in +// layoutForView.sort = "fileName" +// layoutForView.ascending = !layoutForView.ascending +// self.actionMenu(layoutForView: layoutForView) +// } +// ) +// ) +// +// if layoutForView.ascending { +// title = NSLocalizedString("_order_by_date_more_recent_", comment: "") +// icon = UIImage(named: "sortDateMoreRecent")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } else { +// title = NSLocalizedString("_order_by_date_less_recent_", comment: "") +// icon = UIImage(named: "sortDateLessRecent")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// icon = UIImage(named: "sortDateMoreRecent")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } else { +// title = NSLocalizedString("_order_by_date_less_recent_", comment: "") +// icon = UIImage(named: "sortDateLessRecent")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } +// +// actions.append( +// NCMenuAction( +// title: title, +// icon: icon, +// selected: layoutForView.sort == "date", +// on: layoutForView.sort == "date", +// action: { _ in +// layoutForView.sort = "date" +// layoutForView.ascending = !layoutForView.ascending +// self.actionMenu(layoutForView: layoutForView) +// } +// ) +// ) +// +// if layoutForView.ascending { +// title = NSLocalizedString("_order_by_size_largest_", comment: "") +// icon = UIImage(named: "sortLargest")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } else { +// title = NSLocalizedString("_order_by_size_smallest_", comment: "") +// icon = UIImage(named: "sortSmallest")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// icon = UIImage(named: "sortLargest")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } else { +// title = NSLocalizedString("_order_by_size_smallest_", comment: "") +// icon = UIImage(named: "sortSmallest")!.image(color: NCBrandColor.shared.iconImageColor, size: 50) +// } +// +// actions.append( +// NCMenuAction( +// title: title, +// icon: icon, +// selected: layoutForView.sort == "size", +// on: layoutForView.sort == "size", +// action: { _ in +// layoutForView.sort = "size" +// layoutForView.ascending = !layoutForView.ascending +// self.actionMenu(layoutForView: layoutForView) +// } +// ) +// ) +// +// if !hideDirectoryOnTop { +// actions.append( +// NCMenuAction( +// title: NSLocalizedString("_directory_on_top_no_", comment: ""), +// icon: UIImage(named: "foldersOnTop")!.image(color: NCBrandColor.shared.iconImageColor, size: 50), +// icon: UIImage(named: "foldersOnTop")!.image(color: NCBrandColor.shared.iconImageColor, size: 50), +// selected: layoutForView.directoryOnTop, +// on: layoutForView.directoryOnTop, +// action: { _ in +// layoutForView.directoryOnTop = !layoutForView.directoryOnTop +// NCKeychain().setDirectoryOnTop(account: self.account, value: layoutForView.directoryOnTop) +//// NCKeychain().setDirectoryOnTop(account: self.account, value: layoutForView.directoryOnTop) +// self.actionMenu(layoutForView: layoutForView) +// } +// ) +// ) +// } +// +// viewController.presentMenu(with: actions) + } +// + func actionMenu(layoutForView: NCDBLayoutForView) { +// +// switch layoutForView.sort { +// case "fileName": +// layoutForView.titleButtonHeader = layoutForView.ascending ? "_sorted_by_name_a_z_" : "_sorted_by_name_z_a_" +// case "date": +// layoutForView.titleButtonHeader = layoutForView.ascending ? "_sorted_by_date_less_recent_" : "_sorted_by_date_more_recent_" +// case "size": +// layoutForView.titleButtonHeader = layoutForView.ascending ? "_sorted_by_size_smallest_" : "_sorted_by_size_largest_" +// default: +// break +// } +// +// self.sortButton?.setTitle(NSLocalizedString(layoutForView.titleButtonHeader, comment: ""), for: .normal) +// NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView) +// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeLayout, +// object: nil, +// userInfo: ["account": self.account, +// "serverUrl": self.serverUrl, +// "layoutForView": layoutForView]) +// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) + } +} diff --git a/iOSClient/Networking/NCDownloadAction.swift b/iOSClient/Networking/NCDownloadAction.swift new file mode 100644 index 0000000000..982aca2a66 --- /dev/null +++ b/iOSClient/Networking/NCDownloadAction.swift @@ -0,0 +1,740 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2020 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import UIKit +import NextcloudKit +import Queuer +import SVGKit +import Photos +import Alamofire + +class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSelectDelegate, NCTransferDelegate { + static let shared = NCDownloadAction() + + var viewerQuickLook: NCViewerQuickLook? + var documentController: UIDocumentInteractionController? + let utilityFileSystem = NCUtilityFileSystem() + let utility = NCUtility() + let global = NCGlobal.shared + var sceneIdentifier: String = "" + + override private init() { } + + func setup(sceneIdentifier: String) { + self.sceneIdentifier = sceneIdentifier + + Task { + await NCNetworking.shared.transferDispatcher.addDelegate(self) + } + } + + // MARK: - Download + + func transferChange(status: String, metadata: tableMetadata, error: NKError) { + DispatchQueue.main.async { + switch status { + /// DOWNLOADED + case self.global.networkingStatusDownloaded: + self.downloadedFile(metadata: metadata, error: error) + default: + break + } + } + } + + func downloadedFile(metadata: tableMetadata, error: NKError) { + guard error == .success else { + return + } + /// Select UIWindowScene active in serverUrl + var controller: NCMainTabBarController? + let windowScenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene } + if windowScenes.count == 1 { + controller = UIApplication.shared.firstWindow?.rootViewController as? NCMainTabBarController + } else if let sceneIdentifier = metadata.sceneIdentifier, + let tabBarController = SceneManager.shared.getController(sceneIdentifier: sceneIdentifier) { + controller = tabBarController + } else { + for windowScene in windowScenes { + if let rootViewController = windowScene.keyWindow?.rootViewController as? NCMainTabBarController, + rootViewController.currentServerUrl() == metadata.serverUrl { + controller = rootViewController + break + } + } + } + guard let controller else { return } + + switch metadata.sessionSelector { + case NCGlobal.shared.selectorLoadFileQuickLook: + + let fileNamePath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase) + let fileNameTemp = NSTemporaryDirectory() + metadata.fileNameView + let viewerQuickLook = NCViewerQuickLook(with: URL(fileURLWithPath: fileNameTemp), isEditingEnabled: true, metadata: metadata) + if let image = UIImage(contentsOfFile: fileNamePath) { + if let data = image.jpegData(compressionQuality: 1) { + do { + try data.write(to: URL(fileURLWithPath: fileNameTemp)) + } catch { + return + } + } + let navigationController = UINavigationController(rootViewController: viewerQuickLook) + navigationController.modalPresentationStyle = .fullScreen + controller.present(navigationController, animated: true) + } else { + self.utilityFileSystem.copyFile(atPath: fileNamePath, toPath: fileNameTemp) + controller.present(viewerQuickLook, animated: true) + } + + case NCGlobal.shared.selectorLoadFileView: + guard !isAppInBackground + else { + return + } + + if metadata.contentType.contains("opendocument") && !self.utility.isTypeFileRichDocument(metadata) { + self.openActivityViewController(selectedMetadata: [metadata], controller: controller, sender: nil) + } else if metadata.classFile == NKTypeClassFile.compress.rawValue || metadata.classFile == NKTypeClassFile.unknow.rawValue { + self.openActivityViewController(selectedMetadata: [metadata], controller: controller, sender: nil) + } else { + if let viewController = controller.currentViewController() { + let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt1024, userId: metadata.userId, urlBase: metadata.urlBase) + Task { + if let vc = await NCViewer().getViewerController(metadata: metadata, image: image, delegate: viewController) { + await viewController.navigationController?.pushViewController(vc, animated: true) + } + } + } + } + + case NCGlobal.shared.selectorOpenIn: + guard !isAppInBackground + else { + return + } + + self.openActivityViewController(selectedMetadata: [metadata], controller: controller, sender: nil) + + case NCGlobal.shared.selectorPrint: + // waiting close menu + // https://github.com/nextcloud/ios/issues/2278 +// DispatchQueue.main.async { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + self.printDocument(metadata: metadata) + } + + case NCGlobal.shared.selectorSaveAlbum: + + self.saveAlbum(metadata: metadata, controller: controller) + + case NCGlobal.shared.selectorSaveAsScan: + + self.saveAsScan(metadata: metadata, controller: controller) + + case NCGlobal.shared.selectorOpenDetail: + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterOpenMediaDetail, userInfo: ["ocId": metadata.ocId]) + + default: + let applicationHandle = NCApplicationHandle() + applicationHandle.downloadedFile(selector: metadata.sessionSelector, metadata: metadata) + } + } + + // MARK: - + + func setMetadataAvalableOffline(_ metadata: tableMetadata, isOffline: Bool) async { + if isOffline { + if metadata.directory { + await NCManageDatabase.shared.setDirectoryAsync(serverUrl: metadata.serverUrlFileName, offline: false, metadata: metadata) + let predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND sessionSelector == %@ AND status == %d", metadata.account, metadata.serverUrlFileName, NCGlobal.shared.selectorSynchronizationOffline, NCGlobal.shared.metadataStatusWaitDownload) + if let metadatas = await NCManageDatabase.shared.getMetadatasAsync(predicate: predicate) { + await NCManageDatabase.shared.clearMetadatasSessionAsync(metadatas: metadatas) + } + } else { + await NCManageDatabase.shared.setOffLocalFileAsync(ocId: metadata.ocId) + } + } else if metadata.directory { + await NCManageDatabase.shared.cleanTablesOcIds(account: metadata.account, userId: metadata.userId, urlBase: metadata.urlBase) + await NCManageDatabase.shared.setDirectoryAsync(serverUrl: metadata.serverUrlFileName, offline: true, metadata: metadata) + await NCNetworking.shared.synchronization(account: metadata.account, serverUrl: metadata.serverUrlFileName, userId: metadata.userId, urlBase: metadata.urlBase, metadatasInDownload: nil) + } else { + var metadatasSynchronizationOffline: [tableMetadata] = [] + metadatasSynchronizationOffline.append(metadata) + if let metadata = await NCManageDatabase.shared.getMetadataLivePhotoAsync(metadata: metadata) { + metadatasSynchronizationOffline.append(metadata) + } + await NCManageDatabase.shared.addLocalFileAsync(metadata: metadata, offline: true) + for metadata in metadatasSynchronizationOffline { + await NCManageDatabase.shared.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, + session: NCNetworking.shared.sessionDownloadBackground, + selector: NCGlobal.shared.selectorSynchronizationOffline) + } + } + } + + // MARK: - + + @MainActor + func viewerFile(account: String, fileId: String, viewController: UIViewController) async { + var downloadRequest: DownloadRequest? + let hud = NCHud(viewController.tabBarController?.view) + + if let metadata = await NCManageDatabase.shared.getMetadataFromFileIdAsync(fileId) { + do { + let attr = try FileManager.default.attributesOfItem(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase)) + let fileSize = attr[FileAttributeKey.size] as? UInt64 ?? 0 + if fileSize > 0 { + if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: viewController) { + viewController.navigationController?.pushViewController(vc, animated: true) + } + return + } + } catch { + print("Error: \(error)") + } + } + + hud.ringProgress(tapToCancelDetailText: true) { + if let request = downloadRequest { + request.cancel() + } + } + + let resultsFile = await NextcloudKit.shared.getFileFromFileIdAsync(fileId: fileId, account: account) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, + path: fileId, + name: "getFileFromFileId") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + hud.dismiss() + guard resultsFile.error == .success, let file = resultsFile.file else { + NCContentPresenter().showError(error: resultsFile.error) + return + } + + let metadata = await NCManageDatabase.shared.convertFileToMetadataAsync(file) + await NCManageDatabase.shared.addMetadataAsync(metadata) + + let fileNameLocalPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase) + + if metadata.isAudioOrVideo { + if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: viewController) { + viewController.navigationController?.pushViewController(vc, animated: true) + } + return + } + + hud.show() + let download = await NextcloudKit.shared.downloadAsync(serverUrlFileName: metadata.serverUrlFileName, fileNameLocalPath: fileNameLocalPath, account: account) { request in + downloadRequest = request + } taskHandler: { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: metadata.account, + path: metadata.serverUrlFileName, + name: "download") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + + let ocId = metadata.ocId + await NCManageDatabase.shared.setMetadataSessionAsync(ocId: ocId, + sessionTaskIdentifier: task.taskIdentifier, + status: self.global.metadataStatusDownloading) + } + } progressHandler: { progress in + hud.progress(progress.fractionCompleted) + } + + hud.dismiss() + await NCManageDatabase.shared.setMetadataSessionAsync(ocId: metadata.ocId, + session: "", + sessionTaskIdentifier: 0, + sessionError: "", + status: self.global.metadataStatusNormal, + etag: download.etag) + + if download.nkError == .success { + await NCManageDatabase.shared.addLocalFileAsync(metadata: metadata) + if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: viewController) { + viewController.navigationController?.pushViewController(vc, animated: true) + } + } + } + + // MARK: - + + func openShare(viewController: UIViewController, metadata: tableMetadata, page: NCBrandOptions.NCInfoPagingTab) { + var page = page + let capabilities = NCNetworking.shared.capabilities[metadata.account] ?? NKCapabilities.Capabilities() + + NCActivityIndicator.shared.start(backgroundView: viewController.view) + NCNetworking.shared.readFile(serverUrlFileName: metadata.serverUrlFileName, account: metadata.account) { _, metadata, file, error in + Task { @MainActor in + NCActivityIndicator.shared.stop() + + if let metadata = metadata, error == .success { + let shareNavigationController = UIStoryboard(name: "NCShare", bundle: nil).instantiateInitialViewController() as? UINavigationController + let shareViewController = shareNavigationController?.topViewController as? NCShare + shareViewController?.metadata = metadata + shareNavigationController?.modalPresentationStyle = .formSheet + if let shareNavigationController = shareNavigationController { + viewController.present(shareNavigationController, animated: true, completion: nil) + } + } + } + } + } + + // MARK: - Open Activity [Share] ... + + func openActivityViewController(selectedMetadata: [tableMetadata], controller: NCMainTabBarController?, sender: Any?) { + guard let controller else { return } + let metadatas = selectedMetadata.filter({ !$0.directory }) + var urls: [URL] = [] + var downloadMetadata: [(tableMetadata, URL)] = [] + + for metadata in metadatas { + let fileURL = URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase)) + if utilityFileSystem.fileProviderStorageExists(metadata) { + urls.append(fileURL) + } else { + downloadMetadata.append((metadata, fileURL)) + } + } + + let processor = ParallelWorker(n: 5, titleKey: "_downloading_", totalTasks: downloadMetadata.count, controller: controller) + for (metadata, url) in downloadMetadata { + processor.execute { completion in + Task { + guard let metadata = await NCManageDatabase.shared.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, + session: NCNetworking.shared.sessionDownload, + selector: "", + sceneIdentifier: controller.sceneIdentifier) else { + return completion() + } + + await NCNetworking.shared.downloadFile(metadata: metadata) { _ in + } progressHandler: { progress in + processor.hud.progress(progress.fractionCompleted) + } + + if self.utilityFileSystem.fileProviderStorageExists(metadata) { + urls.append(url) + } + completion() + } + } + } + + processor.completeWork { + guard !urls.isEmpty else { return } + let activityViewController = UIActivityViewController(activityItems: urls, applicationActivities: nil) + + // iPad + if let popover = activityViewController.popoverPresentationController { + if let view = sender as? UIView { + popover.sourceView = view + popover.sourceRect = view.bounds + } else { + popover.sourceView = controller.view + popover.sourceRect = CGRect(x: controller.view.bounds.midX, + y: controller.view.bounds.midY, + width: 0, + height: 0) + popover.permittedArrowDirections = [] + } + } + + controller.present(activityViewController, animated: true) + } + } + + // MARK: - Save as scan + + func saveAsScan(metadata: tableMetadata, controller: NCMainTabBarController?) { + let fileNamePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase) + let fileNameDestination = utilityFileSystem.createFileName("scan.png", fileDate: Date(), fileType: PHAssetMediaType.image, notUseMask: true) + let fileNamePathDestination = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryScan, fileName: fileNameDestination) + + utilityFileSystem.copyFile(atPath: fileNamePath, toPath: fileNamePathDestination) + + if let navigationController = UIStoryboard(name: "NCScan", bundle: nil).instantiateInitialViewController() { + navigationController.modalPresentationStyle = UIModalPresentationStyle.pageSheet + let viewController = navigationController.presentedViewController as? NCScan + viewController?.serverUrl = controller?.currentServerUrl() + viewController?.controller = controller + controller?.present(navigationController, animated: true, completion: nil) + } + } + + // MARK: - Save photo + + func saveAlbum(metadata: tableMetadata, controller: NCMainTabBarController?) { + let fileNamePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase) + + NCAskAuthorization().askAuthorizationPhotoLibrary(controller: controller) { hasPermission in + guard hasPermission else { + let error = NKError(errorCode: NCGlobal.shared.errorFileNotSaved, errorDescription: "_access_photo_not_enabled_msg_") + return NCContentPresenter().messageNotification("_access_photo_not_enabled_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error) + } + + let errorSave = NKError(errorCode: NCGlobal.shared.errorFileNotSaved, errorDescription: "_file_not_saved_cameraroll_") + + do { + if metadata.isImage { + let data = try Data(contentsOf: URL(fileURLWithPath: fileNamePath)) + PHPhotoLibrary.shared().performChanges({ + let assetRequest = PHAssetCreationRequest.forAsset() + assetRequest.addResource(with: .photo, data: data, options: nil) + }) { success, _ in + if !success { + NCContentPresenter().messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error) + } + } + } else if metadata.isVideo { + PHPhotoLibrary.shared().performChanges({ + PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: fileNamePath)) + }) { success, _ in + if !success { + NCContentPresenter().messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error) + } + } + } else { + NCContentPresenter().messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error) + return + } + } catch { + NCContentPresenter().messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error) + } + } + } + + // MARK: - Copy & Paste + + func copyPasteboard(pasteboardOcIds: [String], controller: NCMainTabBarController?) { + var items = [[String: Any]]() + let hudView = controller + + // getting file data can take some time and block the main queue + DispatchQueue.global(qos: .userInitiated).async { + var downloadMetadatas: [tableMetadata] = [] + for ocid in pasteboardOcIds { + guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocid) else { continue } + if let pasteboardItem = metadata.toPasteBoardItem() { + items.append(pasteboardItem) + } else { + downloadMetadatas.append(metadata) + } + } + + let processor = ParallelWorker(n: 5, titleKey: "_downloading_", totalTasks: downloadMetadatas.count, controller: controller) + for metadata in downloadMetadatas { + processor.execute { completion in + Task { + guard let metadata = await NCManageDatabase.shared.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, + session: NCNetworking.shared.sessionDownload, + selector: "", + sceneIdentifier: controller?.sceneIdentifier) else { + return completion() + } + + await NCNetworking.shared.downloadFile(metadata: metadata) { _ in + } progressHandler: { progress in + processor.hud.progress(progress.fractionCompleted) + } + + completion() + } + } + } + + processor.completeWork { + items.append(contentsOf: downloadMetadatas.compactMap({ $0.toPasteBoardItem() })) + UIPasteboard.general.setItems(items, options: [:]) + } + } + } + + func pastePasteboard(serverUrl: String, account: String, controller: NCMainTabBarController?) async { + var fractionCompleted: Float = 0 + let processor = ParallelWorker(n: 5, titleKey: "_status_uploading_", totalTasks: nil, controller: controller) + guard let tblAccount = await NCManageDatabase.shared.getTableAccountAsync(account: account) else { + return + } + + func uploadPastePasteboard(fileName: String, serverUrlFileName: String, fileNameLocalPath: String, serverUrl: String, completion: @escaping () -> Void) { + NextcloudKit.shared.upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, account: account) { _ in + } taskHandler: { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, + path: serverUrlFileName, + name: "upload") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } progressHandler: { progress in + if Float(progress.fractionCompleted) > fractionCompleted || fractionCompleted == 0 { + processor.hud.progress(progress.fractionCompleted) + fractionCompleted = Float(progress.fractionCompleted) + } + } completionHandler: { account, ocId, etag, _, _, _, error in + if error == .success && etag != nil && ocId != nil { + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(ocId!, + fileName: fileName, + userId: tblAccount.userId, + urlBase: tblAccount.urlBase) + self.utilityFileSystem.moveFile(atPath: fileNameLocalPath, toPath: toPath) + NCManageDatabase.shared.addLocalFile(account: account, etag: etag!, ocId: ocId!, fileName: fileName) + Task { + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferRequestData(serverUrl: serverUrl) + } + } + } else { + NCContentPresenter().showError(error: error) + } + fractionCompleted = 0 + completion() + } + } + + for (index, items) in UIPasteboard.general.items.enumerated() { + for item in items { + let capabilities = await NKCapabilities.shared.getCapabilities(for: account) + let results = NKFilePropertyResolver().resolve(inUTI: item.key, capabilities: capabilities) + guard let data = UIPasteboard.general.data(forPasteboardType: item.key, inItemSet: IndexSet([index]))?.first else { + continue + } + let fileName = results.name + "_" + NCPreferences().incrementalNumber + "." + results.ext + let serverUrlFileName = utilityFileSystem.createServerUrl(serverUrl: serverUrl, fileName: fileName) + let ocIdUpload = UUID().uuidString + let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(ocIdUpload, + fileName: fileName, + userId: tblAccount.userId, + urlBase: tblAccount.urlBase) + do { try data.write(to: URL(fileURLWithPath: fileNameLocalPath)) } catch { continue } + processor.execute { completion in + uploadPastePasteboard(fileName: fileName, serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, serverUrl: serverUrl, completion: completion) + } + } + } + processor.completeWork() + } + + // MARK: - + + func openFileViewInFolder(serverUrl: String, fileNameBlink: String?, fileNameOpen: String?, sceneIdentifier: String) { + guard let controller = SceneManager.shared.getController(sceneIdentifier: sceneIdentifier), + let navigationController = controller.viewControllers?.first as? UINavigationController + else { return } + let session = NCSession.shared.getSession(controller: controller) + var serverUrlPush = self.utilityFileSystem.getHomeServer(session: session) + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + navigationController.popToRootViewController(animated: false) + controller.selectedIndex = 0 + if serverUrlPush == serverUrl, + let viewController = navigationController.topViewController as? NCFiles { + viewController.blinkCell(fileName: fileNameBlink) + viewController.openFile(fileName: fileNameOpen) + return + } + + let diffDirectory = serverUrl.replacingOccurrences(of: serverUrlPush, with: "") + var subDirs = diffDirectory.split(separator: "/") + + while serverUrlPush != serverUrl, !subDirs.isEmpty { + + guard let dir = subDirs.first else { + return + } + serverUrlPush = self.utilityFileSystem.createServerUrl(serverUrl: serverUrlPush, fileName: String(dir)) + + if let viewController = controller.navigationCollectionViewCommon.first(where: { $0.navigationController == navigationController && $0.serverUrl == serverUrlPush})?.viewController as? NCFiles, viewController.isViewLoaded { + viewController.fileNameBlink = fileNameBlink + viewController.fileNameOpen = fileNameOpen + navigationController.pushViewController(viewController, animated: false) + } else { + if let viewController: NCFiles = UIStoryboard(name: "NCFiles", bundle: nil).instantiateInitialViewController() as? NCFiles { + viewController.serverUrl = serverUrlPush + viewController.titleCurrentFolder = String(dir) + viewController.navigationItem.backButtonTitle = viewController.titleCurrentFolder + + controller.navigationCollectionViewCommon.append(NavigationCollectionViewCommon(serverUrl: serverUrlPush, navigationController: navigationController, viewController: viewController)) + + if serverUrlPush == serverUrl { + viewController.fileNameBlink = fileNameBlink + viewController.fileNameOpen = fileNameOpen + } + navigationController.pushViewController(viewController, animated: false) + } + } + subDirs.remove(at: 0) + } + } + } + + // MARK: - NCSelect + Delegate + + func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], overwrite: Bool, copy: Bool, move: Bool) {//, session: NCSession.Session) { + if let destination = serverUrl, !items.isEmpty { + if copy { + for case let metadata as tableMetadata in items { + if metadata.status != NCGlobal.shared.metadataStatusNormal, metadata.status != NCGlobal.shared.metadataStatusWaitCopy { + continue + } + + NCNetworking.shared.copyMetadata(metadata, destination: destination, overwrite: overwrite) + } + + } else if move { + for case let metadata as tableMetadata in items { + if metadata.status != NCGlobal.shared.metadataStatusNormal, metadata.status != NCGlobal.shared.metadataStatusWaitMove { + continue + } + + NCNetworking.shared.moveMetadata(metadata, destination: destination, overwrite: overwrite) + } + } + } + } + + func openSelectView(items: [tableMetadata], controller: NCMainTabBarController?) { + let session = NCSession.shared.getSession(controller: controller) + let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController + let topViewController = navigationController?.topViewController as? NCSelect + var listViewController = [NCSelect]() + var copyItems: [tableMetadata] = [] + let capabilities = NCNetworking.shared.capabilities[controller?.account ?? ""] ?? NKCapabilities.Capabilities() + + for item in items { + if let fileNameError = FileNameValidator.checkFileName(item.fileNameView, account: controller?.account, capabilities: capabilities) { + controller?.present(UIAlertController.warning(message: "\(fileNameError.errorDescription) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) + return + } + copyItems.append(item) + } + + let home = utilityFileSystem.getHomeServer(session: session) + var serverUrl = copyItems[0].serverUrl + + // Setup view controllers such that the current view is of the same directory the items to be copied are in + while true { + // If not in the topmost directory, create a new view controller and set correct title. + // If in the topmost directory, use the default view controller as the base. + var viewController: NCSelect? + if serverUrl != home { + viewController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateViewController(withIdentifier: "NCSelect.storyboard") as? NCSelect + if viewController == nil { + return + } + viewController!.titleCurrentFolder = (serverUrl as NSString).lastPathComponent + } else { + viewController = topViewController + } + guard let vc = viewController else { return } + + vc.delegate = self + vc.typeOfCommandView = .copyMove + vc.items = copyItems + vc.serverUrl = serverUrl + vc.session = session + + vc.navigationItem.backButtonTitle = vc.titleCurrentFolder + listViewController.insert(vc, at: 0) + + if serverUrl != home { + if let serverDirectoryUp = utilityFileSystem.serverDirectoryUp(serverUrl: serverUrl, home: home) { + serverUrl = serverDirectoryUp + } + } else { + break + } + } + + navigationController?.setViewControllers(listViewController, animated: false) + navigationController?.modalPresentationStyle = .formSheet + + if let navigationController = navigationController { + controller?.present(navigationController, animated: true, completion: nil) + } + } + + // MARK: - Print + + func printDocument(metadata: tableMetadata) { + + let fileNameURL = URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase)) + let printController = UIPrintInteractionController.shared + let printInfo = UIPrintInfo(dictionary: nil) + + printInfo.jobName = fileNameURL.lastPathComponent + printInfo.outputType = metadata.isImage ? .photo : .general + printController.printInfo = printInfo + printController.showsNumberOfCopies = true + + guard !UIPrintInteractionController.canPrint(fileNameURL) else { + printController.printingItem = fileNameURL + printController.present(animated: true) + return + } + + // can't print without data + guard let data = try? Data(contentsOf: fileNameURL) else { return } + + if let svg = SVGKImage(data: data) { + printController.printingItem = svg.uiImage + printController.present(animated: true) + return + } + + guard let text = String(data: data, encoding: .utf8) else { return } + let formatter = UISimpleTextPrintFormatter(text: text) + formatter.perPageContentInsets.top = 72 + formatter.perPageContentInsets.bottom = 72 + formatter.perPageContentInsets.left = 72 + formatter.perPageContentInsets.right = 72 + printController.printFormatter = formatter + printController.present(animated: true) + } +} + +fileprivate extension tableMetadata { + func toPasteBoardItem() -> [String: Any]? { + // Get Data + let fileUrl = URL(fileURLWithPath: NCUtilityFileSystem().getDirectoryProviderStorageOcId(ocId, + fileName: fileNameView, + userId: userId, + urlBase: urlBase)) + guard NCUtilityFileSystem().fileProviderStorageExists(self), + let data = try? Data(contentsOf: fileUrl) else { return nil } + + // Determine the UTI for the file + guard let fileUTI = UTType(filenameExtension: fileExtension)?.identifier else { return nil } + + // Pasteboard item + return [fileUTI: data] + } +} diff --git a/iOSClient/Offline/NCOffline.swift b/iOSClient/Offline/NCOffline.swift index c677762068..2aca7501ca 100644 --- a/iOSClient/Offline/NCOffline.swift +++ b/iOSClient/Offline/NCOffline.swift @@ -34,7 +34,7 @@ class NCOffline: NCCollectionViewCommon { layoutKey = NCGlobal.shared.layoutViewOffline enableSearchBar = false headerRichWorkspaceDisable = true - emptyImageName = "icloud.and.arrow.down" + emptyImageName = "cloudDownload" emptyTitle = "_files_no_files_" emptyDescription = "_tutorial_offline_view_" emptyDataPortaitOffset = 30