From 0e48f421c8440748e2bbf43d5bd3fce1fa990b4b Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:33:00 +0530 Subject: [PATCH 1/4] NMC 2169 - Media theming customisation changes --- iOSClient/AppDelegate.swift | 3 + .../Data/NCManageDatabase+Metadata.swift | 49 +++-- iOSClient/Media/NCMedia.storyboard | 74 +++++-- iOSClient/Media/NCMediaCommandView.xib | 178 +++++++++++++++ iOSClient/Menu/NCMedia+Menu.swift | 203 ++++++++++++++++++ iOSClient/NCImageCache.swift | 13 ++ iOSClient/Settings/NCPreferences.swift | 37 ++++ .../NCViewerMediaDetailView.swift | 1 + .../NCViewerQuickLook/NCViewerQuickLook.swift | 2 +- 9 files changed, 525 insertions(+), 35 deletions(-) create mode 100644 iOSClient/Media/NCMediaCommandView.xib create mode 100644 iOSClient/Menu/NCMedia+Menu.swift diff --git a/iOSClient/AppDelegate.swift b/iOSClient/AppDelegate.swift index 7fcf867ea8..f5698a1076 100644 --- a/iOSClient/AppDelegate.swift +++ b/iOSClient/AppDelegate.swift @@ -38,6 +38,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD await NCAccount().deleteAllAccounts() } } + UINavigationBar.appearance().tintColor = NCBrandColor.shared.customer + UIToolbar.appearance().tintColor = NCBrandColor.shared.customer + let utilityFileSystem = NCUtilityFileSystem() let utility = NCUtility() diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index f03ef6cf8d..d02379169f 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -1213,17 +1213,41 @@ extension NCManageDatabase { } ?? false } - func getMetadataDirectoryAsync(serverUrl: String, account: String) async -> tableMetadata? { - guard let url = URL(string: serverUrl) else { - return nil - } - let fileName = url.lastPathComponent - var baseUrl = url.deletingLastPathComponent().absoluteString - if baseUrl.hasSuffix("/") { - baseUrl.removeLast() - } - guard let decodedBaseUrl = baseUrl.removingPercentEncoding else { - return nil + func createMetadatasFolder(assets: [PHAsset], + useSubFolder: Bool, + session: NCSession.Session, completion: @escaping ([tableMetadata]) -> Void) { + var foldersCreated: Set = [] + var metadatas: [tableMetadata] = [] + let serverUrlBase = getAccountAutoUploadDirectory(session: session) + let fileNameBase = getAccountAutoUploadFileName(account: session.account) + let predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND directory == true", session.account, serverUrlBase) + + func createMetadata(serverUrl: String, fileName: String, metadata: tableMetadata?) { + guard !foldersCreated.contains(serverUrl + "/" + fileName) else { + return + } + foldersCreated.insert(serverUrl + "/" + fileName) + + if let metadata { + metadata.status = NCGlobal.shared.metadataStatusWaitCreateFolder + metadata.sessionSelector = NCGlobal.shared.selectorUploadAutoUpload + metadata.sessionDate = Date() + metadatas.append(tableMetadata(value: metadata)) + } else { + let metadata = NCManageDatabase.shared.createMetadata(fileName: fileName, + fileNameView: fileName, + ocId: NSUUID().uuidString, + serverUrl: serverUrl, + url: "", + contentType: "httpd/unix-directory", + directory: true, + session: session, + sceneIdentifier: nil) + metadata.status = NCGlobal.shared.metadataStatusWaitCreateFolder + metadata.sessionSelector = NCGlobal.shared.selectorUploadAutoUpload + metadata.sessionDate = Date() + metadatas.append(metadata) + } } return await core.performRealmReadAsync { realm in @@ -1233,7 +1257,7 @@ extension NCManageDatabase { return object?.detachedCopy() } } - + func getMetadataDirectory(serverUrl: String, account: String) -> tableMetadata? { guard let url = URL(string: serverUrl) else { return nil @@ -1253,6 +1277,7 @@ extension NCManageDatabase { .first return object?.detachedCopy() } + return nil } func getTransferAsync(tranfersSuccess: [tableMetadata]) async -> [tableMetadata] { diff --git a/iOSClient/Media/NCMedia.storyboard b/iOSClient/Media/NCMedia.storyboard index fcdb7e83f1..517f2fdfb8 100644 --- a/iOSClient/Media/NCMedia.storyboard +++ b/iOSClient/Media/NCMedia.storyboard @@ -1,10 +1,10 @@ - + - + + - @@ -18,7 +18,7 @@ - + @@ -31,43 +31,78 @@ - + - + + + + - - - - + + + + + + + + + - - - + + + + - + + + + @@ -76,9 +111,4 @@ - - - - - diff --git a/iOSClient/Media/NCMediaCommandView.xib b/iOSClient/Media/NCMediaCommandView.xib new file mode 100644 index 0000000000..b2df2d68eb --- /dev/null +++ b/iOSClient/Media/NCMediaCommandView.xib @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Menu/NCMedia+Menu.swift b/iOSClient/Menu/NCMedia+Menu.swift new file mode 100644 index 0000000000..cc1d094e39 --- /dev/null +++ b/iOSClient/Menu/NCMedia+Menu.swift @@ -0,0 +1,203 @@ +// +// NCMedia+Menu.swift +// Nextcloud +// +// Created by Marino Faggiana on 03/03/2021. +// Copyright © 2021 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +import UIKit +import FloatingPanel +import NextcloudKit + +extension NCMedia { + func tapSelect() { + self.isEditMode = false + self.selectOcId.removeAll() + self.selectIndexPath.removeAll() + self.collectionView?.reloadData() + } + + func toggleMenu() { + + var actions: [NCMenuAction] = [] + + defer { presentMenu(with: actions) } + + if !isEditMode { + if let metadatas = self.metadatas, !metadatas.isEmpty { + actions.append( + NCMenuAction( + title: NSLocalizedString("_select_", comment: ""), + icon: utility.loadImage(named: "selectFull", color: NCBrandColor.shared.iconColor), + action: { _ in + self.isEditMode = true + } + ) + ) + } + + actions.append(.seperator(order: 0)) + + actions.append( + NCMenuAction( + title: NSLocalizedString("_media_viewimage_show_", comment: ""), + icon: utility.loadImage(named: showOnlyImages ? "nocamera" : "file_photo_menu",color: NCBrandColor.shared.iconColor), + selected: showOnlyImages, + on: true, + action: { _ in + self.showOnlyImages = true + self.showOnlyVideos = false + self.reloadDataSource() + } + ) + ) + + actions.append( + NCMenuAction( + title: NSLocalizedString("_media_viewvideo_show_", comment: ""), + icon: utility.loadImage(named: showOnlyVideos ? "videono" : "videoyes",color: NCBrandColor.shared.iconColor), + selected: showOnlyVideos, + on: true, + action: { _ in + self.showOnlyImages = false + self.showOnlyVideos = true + self.reloadDataSource() + } + ) + ) + + actions.append( + NCMenuAction( + title: NSLocalizedString("_media_show_all_", comment: ""), + icon: utility.loadImage(named: "photo.on.rectangle.angled", color: NCBrandColor.shared.iconColor), + selected: !showOnlyImages && !showOnlyVideos, + on: true, + action: { _ in + self.showOnlyImages = false + self.showOnlyVideos = false + self.reloadDataSource() + } + ) + ) + + actions.append(.seperator(order: 0)) + + actions.append( + NCMenuAction( + title: NSLocalizedString("_select_media_folder_", comment: ""), + icon: utility.loadImage(named: "folder", color: NCBrandColor.shared.iconColor), + action: { _ in + if let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController, + let viewController = navigationController.topViewController as? NCSelect { + + viewController.delegate = self + viewController.typeOfCommandView = .select + viewController.type = "mediaFolder" + viewController.selectIndexPath = self.selectIndexPath + + self.present(navigationController, animated: true, completion: nil) + } + } + ) + ) + + actions.append( + NCMenuAction( + title: NSLocalizedString("_media_by_modified_date_", comment: ""), + icon: utility.loadImage(named: "sortFileNameAZ", color: NCBrandColor.shared.iconColor), + selected: NCKeychain().mediaSortDate == "date", + on: true, + action: { _ in + NCKeychain().mediaSortDate = "date" + self.reloadDataSource() + } + ) + ) + + actions.append( + NCMenuAction( + title: NSLocalizedString("_media_by_created_date_", comment: ""), + icon: utility.loadImage(named: "sortFileNameAZ", color: NCBrandColor.shared.iconColor), + selected: NCKeychain().mediaSortDate == "creationDate", + on: true, + action: { _ in + NCKeychain().mediaSortDate = "creationDate" + self.reloadDataSource() + } + ) + ) + + actions.append( + NCMenuAction( + title: NSLocalizedString("_media_by_upload_date_", comment: ""), + icon: utility.loadImage(named: "sortFileNameAZ", color: NCBrandColor.shared.iconColor), + selected: NCKeychain().mediaSortDate == "uploadDate", + on: true, + action: { _ in + NCKeychain().mediaSortDate = "uploadDate" + self.reloadDataSource() + } + ) + ) + + } else { + + // + // CANCEL + // + actions.append( + NCMenuAction( + title: NSLocalizedString("_cancel_", comment: ""), + icon: utility.loadImage(named: "xmark", color: NCBrandColor.shared.iconColor), + action: { _ in self.tapSelect() } + ) + ) + + guard !selectOcId.isEmpty else { return } + let selectedMetadatas = selectOcId.compactMap(NCManageDatabase.shared.getMetadataFromOcId) + + // + // OPEN IN + // + actions.append(.openInAction(selectedMetadatas: selectedMetadatas, viewController: self, completion: tapSelect)) + + // + // SAVE TO PHOTO GALLERY + // + actions.append(.saveMediaAction(selectedMediaMetadatas: selectedMetadatas, completion: tapSelect)) + + // + // COPY - MOVE + // + actions.append(.moveOrCopyAction(selectedMetadatas: selectedMetadatas, indexPath: selectIndexPath, completion: tapSelect)) + + // + // COPY + // + actions.append(.copyAction(selectOcId: selectOcId, completion: tapSelect)) + + // + // DELETE + // can't delete from cache because is needed for NCMedia view, and if locked can't delete from server either. + if !selectedMetadatas.contains(where: { $0.lock && $0.lockOwner != appDelegate.userId }) { + actions.append(.deleteAction(selectedMetadatas: selectedMetadatas, indexPath: selectIndexPath, metadataFolder: nil, viewController: self, completion: tapSelect)) + } + } + } +} diff --git a/iOSClient/NCImageCache.swift b/iOSClient/NCImageCache.swift index 3579e08129..f38ad030f7 100644 --- a/iOSClient/NCImageCache.swift +++ b/iOSClient/NCImageCache.swift @@ -93,6 +93,19 @@ final class NCImageCache: @unchecked Sendable { } } + func calculateMaxImages(percentage: Double, imageSizeKB: Double) -> Int { + let totalRamBytes = Double(ProcessInfo.processInfo.physicalMemory) + let cacheSizeBytes = totalRamBytes * (percentage / 100.0) + let imageSizeBytes = imageSizeKB * 1024 + let maxImages = Int(cacheSizeBytes / imageSizeBytes) + + return maxImages + } + + func getMediaMetadatas(account: String, predicate: NSPredicate? = nil) -> ThreadSafeArray? { + return NCManageDatabase.shared.getMediaMetadatas(predicate: predicate ?? predicateBoth, sorted: "date") + } + func allowExtensions(ext: String) -> Bool { return allowExtensions.contains(ext) } diff --git a/iOSClient/Settings/NCPreferences.swift b/iOSClient/Settings/NCPreferences.swift index 1b49efbae5..f593f8455d 100644 --- a/iOSClient/Settings/NCPreferences.swift +++ b/iOSClient/Settings/NCPreferences.swift @@ -225,6 +225,43 @@ final class NCPreferences: NSObject { } } + var mediaColumnCount: Int { + get { + if let value = try? keychain.get("mediaColumnCount"), let result = Int(value) { + return result + } + return 3 + } + set { + keychain["mediaColumnCount"] = String(newValue) + } + } + + var mediaTypeLayout: String { + get { + if let value = try? keychain.get("mediaTypeLayout") { + return value + } + return NCGlobal.shared.mediaLayoutRatio + } + set { + keychain["mediaTypeLayout"] = String(newValue) + } + } + + var mediaSortDate: String { + get { + migrate(key: "mediaSortDate") + if let value = try? keychain.get("mediaSortDate") { + return value + } + return "date" + } + set { + keychain["mediaSortDate"] = newValue + } + } + var textRecognitionStatus: Bool { get { return getBoolPreference(key: "textRecognitionStatus", defaultValue: false) diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift index 0cf1201651..9c2c44cc3b 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift @@ -188,6 +188,7 @@ class NCViewerMediaDetailView: UIView { } if metadata.isImage && !utilityFileSystem.fileProviderStorageExists(metadata) && metadata.session.isEmpty { + downloadImageButton.tintColor = NCBrandColor.shared.brand downloadImageButton.setTitle(NSLocalizedString("_try_download_full_resolution_", comment: ""), for: .normal) downloadImageLabel.text = NSLocalizedString("_full_resolution_image_info_", comment: "") downloadImageButtonContainer.isHidden = false diff --git a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift index 3f4eaea468..2386e611ca 100644 --- a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift +++ b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift @@ -158,7 +158,7 @@ private var hasChangesQuickLook: Bool = false toolbarConfig.optionButtonFontSize = 16 toolbarConfig.optionButtonFontSizeForPad = 21 toolbarConfig.backgroundColor = .systemGray6 - toolbarConfig.foregroundColor = .systemBlue + toolbarConfig.foregroundColor = NCBrandColor.shared.customer var viewConfig = CropViewConfig() viewConfig.cropMaskVisualEffectType = .none From 5ebd8adacd1b6bfca03513b15d08b17f07e2bc34 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 14 Apr 2025 11:39:24 +0530 Subject: [PATCH 2/4] NMC 2169 - Media theming changes --- Nextcloud.xcodeproj/project.pbxproj | 12 + .../Data/NCManageDatabase+Metadata.swift | 143 ++++- iOSClient/Menu/NCMedia+Menu.swift | 65 ++- iOSClient/NCImageCache.swift | 8 + iOSClient/Settings/NCPreferences.swift | 510 +++++++++++------- 5 files changed, 503 insertions(+), 235 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 8dd4c5545f..87271e11c7 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 */; }; + B5C980182DACD51C0041B146 /* NCMediaCommandView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5C980172DACD51C0041B146 /* NCMediaCommandView.xib */; }; + B5C9801A2DACD56C0041B146 /* NCMedia+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C980192DACD56C0041B146 /* NCMedia+Menu.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 */; }; F321DA8A2B71205A00DDA0E6 /* NCTrashSelectTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F321DA892B71205A00DDA0E6 /* NCTrashSelectTabBar.swift */; }; @@ -1221,6 +1226,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 = ""; }; + B5C980172DACD51C0041B146 /* NCMediaCommandView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCMediaCommandView.xib; sourceTree = ""; }; + B5C980192DACD56C0041B146 /* NCMedia+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCMedia+Menu.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 = ""; }; @@ -1986,6 +1994,7 @@ F376A3732E5CC5FF0067EE25 /* ContextMenuActions.swift */, F77A697C250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift */, F78C6FDD296D677300C952C3 /* NCContextMenu.swift */, + B5C980192DACD56C0041B146 /* NCMedia+Menu.swift */, 3704EB2923D5A58400455C5B /* NCMenu.storyboard */, 371B5A2D23D0B04500FAFAE9 /* NCMenu.swift */, AF935066276B84E700BD078F /* NCMenu+FloatingPanel.swift */, @@ -3125,6 +3134,7 @@ F7EC9CB921185F2000F1C5CE /* Media */ = { isa = PBXGroup; children = ( + B5C980172DACD51C0041B146 /* NCMediaCommandView.xib */, F720B5B72507B9A5008C94E5 /* Cell */, F7501C302212E57400FB1415 /* NCMedia.storyboard */, F7501C312212E57400FB1415 /* NCMedia.swift */, @@ -3946,6 +3956,7 @@ F74C0437253F1CDC009762AB /* NCShares.storyboard in Resources */, F7F4F10C27ECDBDB008676F9 /* Inconsolata-Regular.ttf in Resources */, F7B8B83025681C3400967775 /* GoogleService-Info.plist in Resources */, + B5C980182DACD51C0041B146 /* NCMediaCommandView.xib in Resources */, F7381EE5218218C9000B1560 /* NCOffline.storyboard in Resources */, F768822D2C0DD1E7001CF441 /* Acknowledgements.rtf in Resources */, F76D3CF32428B94E005DFA87 /* NCViewerPDFSearchCell.xib in Resources */, @@ -4704,6 +4715,7 @@ F758B45E212C569D00515F55 /* NCScanCell.swift in Sources */, F78B87E72B62527100C65ADC /* NCMediaDataSource.swift in Sources */, F76882272C0DD1E7001CF441 /* NCManageE2EEView.swift in Sources */, + B5C9801A2DACD56C0041B146 /* NCMedia+Menu.swift in Sources */, F7864ACC2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */, F7327E302B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */, F7D61EA82EBF1694007F865B /* NCManageDatabase+TableCapabilities.swift in Sources */, diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index d02379169f..292d90df27 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -139,9 +139,36 @@ extension tableMetadata { (fileNameView as NSString).deletingPathExtension } + var isRenameable: Bool { + if !NCMetadataPermissions.canRename(self) { + return false + } + if lock { + return false + } + if !isDirectoryE2EE && e2eEncrypted { + return false + } + return true + } + + var isPrintable: Bool { + if isDocumentViewableOnly { + return false + } + if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKCommon.TypeClassFile.image.rawValue { + return true + } + return false + } + var isSavebleInCameraRoll: Bool { return (classFile == NKTypeClassFile.image.rawValue && contentType != "image/svg+xml") || classFile == NKTypeClassFile.video.rawValue } + + var isDocumentViewableOnly: Bool { + sharePermissionsCollaborationServices == NCPermissions().permissionReadShare && classFile == NKCommon.TypeClassFile.document.rawValue + } var isAudioOrVideo: Bool { return classFile == NKTypeClassFile.audio.rawValue || classFile == NKTypeClassFile.video.rawValue @@ -168,7 +195,7 @@ extension tableMetadata { } var isCopyableInPasteboard: Bool { - !directory + !isDocumentViewableOnly && !directory } #if !EXTENSION_FILE_PROVIDER_EXTENSION @@ -177,11 +204,11 @@ extension tableMetadata { } var isCopyableMovable: Bool { - !isDirectoryE2EE && !e2eEncrypted + !isDocumentViewableOnly && !isDirectoryE2EE && !e2eEncrypted } var isModifiableWithQuickLook: Bool { - if directory || isDirectoryE2EE { + if directory || isDocumentViewableOnly || isDirectoryE2EE { return false } return isPDF || isImage @@ -212,18 +239,70 @@ extension tableMetadata { } var canSetAsAvailableOffline: Bool { - return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted +// return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted + return session.isEmpty && !isDocumentViewableOnly } - // Return if is sharable - func isSharable() -> Bool { - guard let capabilities = NCNetworking.shared.capabilities[account] else { - return false - } - if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE) { + var canShare: Bool { + return session.isEmpty && !isDocumentViewableOnly && !directory && !NCBrandOptions.shared.disable_openin_file + } + + var canUnsetDirectoryAsE2EE: Bool { + return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) + } + + var canOpenExternalEditor: Bool { + if isDocumentViewableOnly { return false } - return true + let utility = NCUtility() + let editors = utility.editorsDirectEditing(account: account, contentType: contentType) + let isRichDocument = utility.isTypeFileRichDocument(self) + return classFile == NKCommon.TypeClassFile.document.rawValue && editors.contains(NCGlobal.shared.editorText) && ((editors.contains(NCGlobal.shared.editorOnlyoffice) || isRichDocument)) + } + + var isWaitingTransfer: Bool { + status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusWaitUpload || status == NCGlobal.shared.metadataStatusUploadError + } + + var isInTransfer: Bool { + status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading + } + + var isTransferInForeground: Bool { + (status > 0 && (chunk > 0 || e2eEncrypted)) + } + + var isDownloadUpload: Bool { + status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading + } + + var isDownload: Bool { + status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusDownloading + } + + var isUpload: Bool { + status == NCGlobal.shared.metadataStatusWaitUpload || status == NCGlobal.shared.metadataStatusUploading + } + + var isDirectory: Bool { + directory + } + + @objc var isDirectoryE2EE: Bool { + return NCUtilityFileSystem().isDirectoryE2EE(serverUrl: serverUrl, urlBase: urlBase, userId: userId, account: account) + } + + var isLivePhoto: Bool { + !livePhotoFile.isEmpty + } + + var isNotFlaggedAsLivePhotoByServer: Bool { + !isFlaggedAsLivePhotoByServer + } + + var imageSize: CGSize { + CGSize(width: width, height: height) } var hasPreviewBorder: Bool { @@ -333,6 +412,17 @@ extension tableMetadata { return !lock || (lockOwner == user && lockOwnerType == 0) } + // Return if is sharable + func isSharable() -> Bool { + guard let capabilities = NCNetworking.shared.capabilities[account] else { + return false + } + if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE) { + return false + } + return !e2eEncrypted + } + /// Returns a detached (unmanaged) deep copy of the current `tableMetadata` object. /// /// - Note: The Realm `List` properties containing primitive types (e.g., `tags`, `shareType`) are copied automatically @@ -1105,6 +1195,37 @@ extension NCManageDatabase { } ?? [] } + func getTableMetadatasDirectoryFavoriteIdentifierRankAsync(account: String) async -> [String: NSNumber] { + let result = await performRealmReadAsync { realm in + var listIdentifierRank: [String: NSNumber] = [:] + var counter = Int64(10) + + let results = realm.objects(tableMetadata.self) + .filter("account == %@ AND directory == true AND favorite == true", account) + .sorted(byKeyPath: "fileNameView", ascending: true) + + results.forEach { item in + counter += 1 + listIdentifierRank[item.ocId] = NSNumber(value: counter) + } + + return listIdentifierRank + } + return result ?? [:] + } + + @objc func clearMetadatasUpload(account: String) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter("account == %@ AND (status == %d OR status == %d)", account, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploadError) + realm.delete(results) + } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") + } + } + func getAssetLocalIdentifiersUploadedAsync() async -> [String]? { return await core.performRealmReadAsync { realm in let results = realm.objects(tableMetadata.self).filter("assetLocalIdentifier != ''") diff --git a/iOSClient/Menu/NCMedia+Menu.swift b/iOSClient/Menu/NCMedia+Menu.swift index cc1d094e39..72c6cec47b 100644 --- a/iOSClient/Menu/NCMedia+Menu.swift +++ b/iOSClient/Menu/NCMedia+Menu.swift @@ -28,11 +28,19 @@ import NextcloudKit extension NCMedia { func tapSelect() { self.isEditMode = false - self.selectOcId.removeAll() - self.selectIndexPath.removeAll() + self.fileSelect.removeAll() self.collectionView?.reloadData() } + func selectAll() { + if !fileSelect.isEmpty, self.dataSource.metadatas.count == fileSelect.count { + fileSelect = [] + } else { + fileSelect = self.dataSource.metadatas.compactMap({ $0.ocId }) + } + self.collectionView.reloadData() + } + func toggleMenu() { var actions: [NCMenuAction] = [] @@ -44,7 +52,7 @@ extension NCMedia { actions.append( NCMenuAction( title: NSLocalizedString("_select_", comment: ""), - icon: utility.loadImage(named: "selectFull", color: NCBrandColor.shared.iconColor), + icon: utility.loadImage(named: "selectFull", colors: [NCBrandColor.shared.iconColor]), action: { _ in self.isEditMode = true } @@ -57,13 +65,13 @@ extension NCMedia { actions.append( NCMenuAction( title: NSLocalizedString("_media_viewimage_show_", comment: ""), - icon: utility.loadImage(named: showOnlyImages ? "nocamera" : "file_photo_menu",color: NCBrandColor.shared.iconColor), + icon: utility.loadImage(named: showOnlyImages ? "nocamera" : "file_photo_menu", colors: [NCBrandColor.shared.iconColor]), selected: showOnlyImages, on: true, action: { _ in self.showOnlyImages = true self.showOnlyVideos = false - self.reloadDataSource() + self.loadDataSource() } ) ) @@ -71,13 +79,13 @@ extension NCMedia { actions.append( NCMenuAction( title: NSLocalizedString("_media_viewvideo_show_", comment: ""), - icon: utility.loadImage(named: showOnlyVideos ? "videono" : "videoyes",color: NCBrandColor.shared.iconColor), + icon: utility.loadImage(named: showOnlyVideos ? "videono" : "videoyes", colors: [NCBrandColor.shared.iconColor]), selected: showOnlyVideos, on: true, action: { _ in self.showOnlyImages = false self.showOnlyVideos = true - self.reloadDataSource() + self.loadDataSource() } ) ) @@ -85,13 +93,13 @@ extension NCMedia { actions.append( NCMenuAction( title: NSLocalizedString("_media_show_all_", comment: ""), - icon: utility.loadImage(named: "photo.on.rectangle.angled", color: NCBrandColor.shared.iconColor), + icon: utility.loadImage(named: "photo.on.rectangle.angled", colors: [NCBrandColor.shared.iconColor]), selected: !showOnlyImages && !showOnlyVideos, on: true, action: { _ in self.showOnlyImages = false self.showOnlyVideos = false - self.reloadDataSource() + self.loadDataSource() } ) ) @@ -101,7 +109,7 @@ extension NCMedia { actions.append( NCMenuAction( title: NSLocalizedString("_select_media_folder_", comment: ""), - icon: utility.loadImage(named: "folder", color: NCBrandColor.shared.iconColor), + icon: utility.loadImage(named: "folder", colors: [NCBrandColor.shared.iconColor]), action: { _ in if let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController, let viewController = navigationController.topViewController as? NCSelect { @@ -109,7 +117,6 @@ extension NCMedia { viewController.delegate = self viewController.typeOfCommandView = .select viewController.type = "mediaFolder" - viewController.selectIndexPath = self.selectIndexPath self.present(navigationController, animated: true, completion: nil) } @@ -120,12 +127,12 @@ extension NCMedia { actions.append( NCMenuAction( title: NSLocalizedString("_media_by_modified_date_", comment: ""), - icon: utility.loadImage(named: "sortFileNameAZ", color: NCBrandColor.shared.iconColor), + icon: utility.loadImage(named: "sortFileNameAZ", colors: [NCBrandColor.shared.iconColor]), selected: NCKeychain().mediaSortDate == "date", on: true, action: { _ in NCKeychain().mediaSortDate = "date" - self.reloadDataSource() + self.loadDataSource() } ) ) @@ -133,12 +140,12 @@ extension NCMedia { actions.append( NCMenuAction( title: NSLocalizedString("_media_by_created_date_", comment: ""), - icon: utility.loadImage(named: "sortFileNameAZ", color: NCBrandColor.shared.iconColor), + icon: utility.loadImage(named: "sortFileNameAZ", colors: [NCBrandColor.shared.iconColor]), selected: NCKeychain().mediaSortDate == "creationDate", on: true, action: { _ in NCKeychain().mediaSortDate = "creationDate" - self.reloadDataSource() + self.loadDataSource() } ) ) @@ -146,12 +153,12 @@ extension NCMedia { actions.append( NCMenuAction( title: NSLocalizedString("_media_by_upload_date_", comment: ""), - icon: utility.loadImage(named: "sortFileNameAZ", color: NCBrandColor.shared.iconColor), + icon: utility.loadImage(named: "sortFileNameAZ", colors: [NCBrandColor.shared.iconColor]), selected: NCKeychain().mediaSortDate == "uploadDate", on: true, action: { _ in NCKeychain().mediaSortDate = "uploadDate" - self.reloadDataSource() + self.loadDataSource() } ) ) @@ -164,39 +171,45 @@ extension NCMedia { actions.append( NCMenuAction( title: NSLocalizedString("_cancel_", comment: ""), - icon: utility.loadImage(named: "xmark", color: NCBrandColor.shared.iconColor), + icon: utility.loadImage(named: "xmark", colors: [NCBrandColor.shared.iconColor]), action: { _ in self.tapSelect() } ) ) - guard !selectOcId.isEmpty else { return } - let selectedMetadatas = selectOcId.compactMap(NCManageDatabase.shared.getMetadataFromOcId) + if fileSelect.count != dataSource.metadatas.count { + actions.append(.selectAllAction(action: selectAll)) + } + guard !fileSelect.isEmpty else { return } + + actions.append(.seperator(order: 0)) + + let selectedMetadatas = fileSelect.compactMap(NCManageDatabase.shared.getMetadataFromOcId) // // OPEN IN // - actions.append(.openInAction(selectedMetadatas: selectedMetadatas, viewController: self, completion: tapSelect)) + actions.append(.openInAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: tapSelect)) // // SAVE TO PHOTO GALLERY // - actions.append(.saveMediaAction(selectedMediaMetadatas: selectedMetadatas, completion: tapSelect)) + actions.append(.saveMediaAction(selectedMediaMetadatas: selectedMetadatas, controller: self.controller, completion: tapSelect)) // // COPY - MOVE // - actions.append(.moveOrCopyAction(selectedMetadatas: selectedMetadatas, indexPath: selectIndexPath, completion: tapSelect)) + actions.append(.moveOrCopyAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: tapSelect)) // // COPY // - actions.append(.copyAction(selectOcId: selectOcId, completion: tapSelect)) + actions.append(.copyAction(fileSelect: fileSelect, controller: self.controller, completion: tapSelect)) // // DELETE // can't delete from cache because is needed for NCMedia view, and if locked can't delete from server either. - if !selectedMetadatas.contains(where: { $0.lock && $0.lockOwner != appDelegate.userId }) { - actions.append(.deleteAction(selectedMetadatas: selectedMetadatas, indexPath: selectIndexPath, metadataFolder: nil, viewController: self, completion: tapSelect)) + if !selectedMetadatas.contains(where: { $0.lock && $0.lockOwner != session.userId }) { + actions.append(.deleteAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: tapSelect)) } } } diff --git a/iOSClient/NCImageCache.swift b/iOSClient/NCImageCache.swift index f38ad030f7..e9dcccd855 100644 --- a/iOSClient/NCImageCache.swift +++ b/iOSClient/NCImageCache.swift @@ -29,6 +29,11 @@ final class NCImageCache: @unchecked Sendable { public var isLoadingCache: Bool = false public var controller: UITabBarController? + var createMediaCacheInProgress: Bool = false + let showAllPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == '\(NKCommon.TypeClassFile.image.rawValue)' OR classFile == '\(NKCommon.TypeClassFile.video.rawValue)') AND NOT (session CONTAINS[c] 'upload')" + let showBothPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == '\(NKCommon.TypeClassFile.image.rawValue)' OR classFile == '\(NKCommon.TypeClassFile.video.rawValue)') AND NOT (session CONTAINS[c] 'upload') AND NOT (livePhotoFile != '' AND classFile == '\(NKCommon.TypeClassFile.video.rawValue)')" + let showOnlyPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload') AND NOT (livePhotoFile != '' AND classFile == '\(NKCommon.TypeClassFile.video.rawValue)')" + init() { observerToken = NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: nil) { _ in self.cache.removeAll() @@ -103,6 +108,9 @@ final class NCImageCache: @unchecked Sendable { } func getMediaMetadatas(account: String, predicate: NSPredicate? = nil) -> ThreadSafeArray? { + guard let tableAccount = NCManageDatabase.shared.getTableAccount(predicate: NSPredicate(format: "account == %@", account)) else { return nil } + let startServerUrl = NCUtilityFileSystem().getHomeServer(urlBase: tableAccount.urlBase, userId: tableAccount.userId) + tableAccount.mediaPath + let predicateBoth = NSPredicate(format: showBothPredicateMediaString, account, startServerUrl) return NCManageDatabase.shared.getMediaMetadatas(predicate: predicate ?? predicateBoth, sorted: "date") } diff --git a/iOSClient/Settings/NCPreferences.swift b/iOSClient/Settings/NCPreferences.swift index f593f8455d..23bc167e35 100644 --- a/iOSClient/Settings/NCPreferences.swift +++ b/iOSClient/Settings/NCPreferences.swift @@ -1,44 +1,72 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2023 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCKeychain.swift +// Nextcloud +// +// Created by Marino Faggiana on 23/10/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// import Foundation import UIKit import KeychainAccess import NextcloudKit -final class NCPreferences: NSObject { +@objc class NCKeychain: NSObject { let keychain = Keychain(service: "com.nextcloud.keychain") var showDescription: Bool { get { - return getBoolPreference(key: "showDescription", defaultValue: true) + if let value = try? keychain.get("showDescription"), let result = Bool(value) { + return result + } + return true } set { - setUserDefaults(newValue, forKey: "showDescription") + keychain["showDescription"] = String(newValue) } } var showRecommendedFiles: Bool { get { - return getBoolPreference(key: "showRecommendedFiles", defaultValue: true) + if let value = try? keychain.get("showRecommendedFiles"), let result = Bool(value) { + return result + } + return true } set { - setUserDefaults(newValue, forKey: "showRecommendedFiles") + keychain["showRecommendedFiles"] = String(newValue) } } var typeFilterScanDocument: NCGlobal.TypeFilterScanDocument { get { - let rawValue = getStringPreference(key: "ScanDocumentTypeFilter", defaultValue: NCGlobal.TypeFilterScanDocument.original.rawValue) - return NCGlobal.TypeFilterScanDocument(rawValue: rawValue) ?? .original + if let rawValue = try? keychain.get("ScanDocumentTypeFilter"), let value = NCGlobal.TypeFilterScanDocument(rawValue: rawValue) { + return value + } else { + return .original + } } set { - setUserDefaults(newValue.rawValue, forKey: "ScanDocumentTypeFilter") + keychain["ScanDocumentTypeFilter"] = newValue.rawValue } } - var passcode: String? { + @objc var passcode: String? { get { migrate(key: "passcodeBlock") if let value = try? keychain.get("passcodeBlock"), !value.isEmpty { @@ -51,7 +79,7 @@ final class NCPreferences: NSObject { } } - var resetAppCounterFail: Bool { + @objc var resetAppCounterFail: Bool { get { if let value = try? keychain.get("resetAppCounterFail"), let result = Bool(value) { return result @@ -87,7 +115,7 @@ final class NCPreferences: NSObject { } } - var requestPasscodeAtStart: Bool { + @objc var requestPasscodeAtStart: Bool { get { let keychainOLD = Keychain(service: "Crypto Cloud") if let value = keychainOLD["notPasscodeAtStart"], !value.isEmpty { @@ -110,7 +138,7 @@ final class NCPreferences: NSObject { } } - var touchFaceID: Bool { + @objc var touchFaceID: Bool { get { migrate(key: "enableTouchFaceID") if let value = try? keychain.get("enableTouchFaceID"), let result = Bool(value) { @@ -127,101 +155,151 @@ final class NCPreferences: NSObject { return passcode != nil && requestPasscodeAtStart } - var incrementalNumber: String { + @objc var incrementalNumber: String { + migrate(key: "incrementalnumber") var incrementalString = String(format: "%04ld", 0) - let value = getStringPreference(key: "incrementalnumber", defaultValue: incrementalString) - if var intValue = Int(value) { - intValue += 1 - incrementalString = String(format: "%04ld", intValue) + if let value = try? keychain.get("incrementalnumber"), var result = Int(value) { + result += 1 + incrementalString = String(format: "%04ld", result) } - setUserDefaults(incrementalString, forKey: "incrementalnumber") + keychain["incrementalnumber"] = incrementalString return incrementalString } - var formatCompatibility: Bool { + @objc var showHiddenFiles: Bool { get { - return getBoolPreference(key: "formatCompatibility", defaultValue: true) + migrate(key: "showHiddenFiles") + if let value = try? keychain.get("showHiddenFiles"), let result = Bool(value) { + return result + } + return false } set { - setUserDefaults(newValue, forKey: "formatCompatibility") + keychain["showHiddenFiles"] = String(newValue) } } - var disableFilesApp: Bool { + @objc var formatCompatibility: Bool { get { - return getBoolPreference(key: "disablefilesapp", defaultValue: false) + migrate(key: "formatCompatibility") + if let value = try? keychain.get("formatCompatibility"), let result = Bool(value) { + return result + } + return true } set { - setUserDefaults(newValue, forKey: "disablefilesapp") + keychain["formatCompatibility"] = String(newValue) } } - var livePhoto: Bool { + @objc var disableFilesApp: Bool { get { - return getBoolPreference(key: "livePhoto", defaultValue: true) + migrate(key: "disablefilesapp") + if let value = try? keychain.get("disablefilesapp"), let result = Bool(value) { + return result + } + return false } set { - setUserDefaults(newValue, forKey: "livePhoto") + keychain["disablefilesapp"] = String(newValue) } } - var disableCrashservice: Bool { + @objc var livePhoto: Bool { get { - return getBoolPreference(key: "crashservice", defaultValue: false) + migrate(key: "livePhoto") + if let value = try? keychain.get("livePhoto"), let result = Bool(value) { + return result + } + return true + } + set { + keychain["livePhoto"] = String(newValue) + } + } + + @objc var disableCrashservice: Bool { + get { + migrate(key: "crashservice") + if let value = try? keychain.get("crashservice"), let result = Bool(value) { + return result + } + return false } set { - setUserDefaults(newValue, forKey: "crashservice") + keychain["crashservice"] = String(newValue) } } /// Stores and retrieves the current log level from the keychain. var log: NKLogLevel { +// @objc var logLevel: Int { get { - let value = getIntPreference(key: "logLevel", defaultValue: NKLogLevel.normal.rawValue) - return NKLogLevel(rawValue: value) ?? NKLogLevel.normal + migrate(key: "logLevel") + if let value = try? keychain.get("logLevel"), + let intValue = Int(value), + let level = NKLogLevel(rawValue: intValue) { + return level + } + return NKLogLevel.normal } set { - setUserDefaults(newValue.rawValue, forKey: "logLevel") + keychain["logLevel"] = String(newValue.rawValue) } } - var accountRequest: Bool { + @objc var accountRequest: Bool { get { - return getBoolPreference(key: "accountRequest", defaultValue: false) + migrate(key: "accountRequest") + if let value = try? keychain.get("accountRequest"), let result = Bool(value) { + return result + } + return false } set { - setUserDefaults(newValue, forKey: "accountRequest") + keychain["accountRequest"] = String(newValue) } } - var removePhotoCameraRoll: Bool { + @objc var removePhotoCameraRoll: Bool { get { - return getBoolPreference(key: "removePhotoCameraRoll", defaultValue: false) + migrate(key: "removePhotoCameraRoll") + if let value = try? keychain.get("removePhotoCameraRoll"), let result = Bool(value) { + return result + } + return false } set { - setUserDefaults(newValue, forKey: "removePhotoCameraRoll") + keychain["removePhotoCameraRoll"] = String(newValue) } } - var privacyScreenEnabled: Bool { + @objc var privacyScreenEnabled: Bool { get { + migrate(key: "privacyScreen") if NCBrandOptions.shared.enforce_privacyScreenEnabled { return true } - return getBoolPreference(key: "privacyScreen", defaultValue: false) + if let value = try? keychain.get("privacyScreen"), let result = Bool(value) { + return result + } + return false } set { - setUserDefaults(newValue, forKey: "privacyScreen") + keychain["privacyScreen"] = String(newValue) } } - var cleanUpDay: Int { + @objc var cleanUpDay: Int { get { - let value = getIntPreference(key: "cleanUpDay", defaultValue: NCBrandOptions.shared.cleanUpDay) - return value + migrate(key: "cleanUpDay") + if let value = try? keychain.get("cleanUpDay"), let result = Int(value) { + return result + } + return NCBrandOptions.shared.cleanUpDay } set { - setUserDefaults(newValue, forKey: "cleanUpDay") + keychain["cleanUpDay"] = String(newValue) } } @@ -264,124 +342,144 @@ final class NCPreferences: NSObject { var textRecognitionStatus: Bool { get { - return getBoolPreference(key: "textRecognitionStatus", defaultValue: false) + migrate(key: "textRecognitionStatus") + if let value = try? keychain.get("textRecognitionStatus"), let result = Bool(value) { + return result + } + return false } set { - setUserDefaults(newValue, forKey: "textRecognitionStatus") + keychain["textRecognitionStatus"] = String(newValue) } } var deleteAllScanImages: Bool { get { - return getBoolPreference(key: "deleteAllScanImages", defaultValue: false) + migrate(key: "deleteAllScanImages") + if let value = try? keychain.get("deleteAllScanImages"), let result = Bool(value) { + return result + } + return false } set { - setUserDefaults(newValue, forKey: "deleteAllScanImages") + keychain["deleteAllScanImages"] = String(newValue) } } var qualityScanDocument: Double { get { - let value = getIntPreference(key: "qualityScanDocument", defaultValue: 2) - return Double(value) + migrate(key: "qualityScanDocument") + if let value = try? keychain.get("qualityScanDocument"), let result = Double(value) { + return result + } + return 2 } set { - setUserDefaults(newValue, forKey: "qualityScanDocument") + keychain["qualityScanDocument"] = String(newValue) } } var appearanceAutomatic: Bool { get { - let value = getBoolPreference(key: "appearanceAutomatic", defaultValue: true) - return value + if let value = try? keychain.get("appearanceAutomatic"), let result = Bool(value) { + return result + } + return true } set { - setUserDefaults(newValue, forKey: "appearanceAutomatic") + keychain["appearanceAutomatic"] = String(newValue) } } var appearanceInterfaceStyle: UIUserInterfaceStyle { get { - let value = getStringPreference(key: "appearanceInterfaceStyle", defaultValue: "light") - if value == "light" { - return .light - } else { - return .dark + if let value = try? keychain.get("appearanceInterfaceStyle") { + if value == "light" { + return .light + } else { + return .dark + } } + return .light } set { if newValue == .light { - setUserDefaults("light", forKey: "appearanceInterfaceStyle") + keychain["appearanceInterfaceStyle"] = "light" } else { - setUserDefaults("dark", forKey: "appearanceInterfaceStyle") + keychain["appearanceInterfaceStyle"] = "dark" } } } var screenAwakeMode: AwakeMode { get { - let value = getStringPreference(key: "screenAwakeMode", defaultValue: "off") - if value == "off" { - return .off - } else if value == "on" { - return .on - } else { - return .whileCharging + if let value = try? keychain.get("screenAwakeMode") { + if value == "off" { + return .off + } else if value == "on" { + return .on + } else { + return .whileCharging + } } + return .off } set { if newValue == .off { - setUserDefaults("off", forKey: "screenAwakeMode") + keychain["screenAwakeMode"] = "off" } else if newValue == .on { - setUserDefaults("on", forKey: "screenAwakeMode") + keychain["screenAwakeMode"] = "on" } else { - setUserDefaults("whileCharging", forKey: "screenAwakeMode") + keychain["screenAwakeMode"] = "whileCharging" } } } var fileNameType: Bool { get { - return getBoolPreference(key: "fileNameType", defaultValue: false) + if let value = try? keychain.get("fileNameType"), let result = Bool(value) { + return result + } + return false } set { - setUserDefaults(newValue, forKey: "fileNameType") + keychain["fileNameType"] = String(newValue) } } var fileNameOriginal: Bool { get { - return getBoolPreference(key: "fileNameOriginal", defaultValue: false) + if let value = try? keychain.get("fileNameOriginal"), let result = Bool(value) { + return result + } + return false } set { - setUserDefaults(newValue, forKey: "fileNameOriginal") + keychain["fileNameOriginal"] = String(newValue) } } var fileNameMask: String { get { - return getStringPreference(key: "fileNameMask", defaultValue: "") + if let value = try? keychain.get("fileNameMask") { + return value + } + return "" } set { - setUserDefaults(newValue, forKey: "fileNameMask") + keychain["fileNameMask"] = String(newValue) } } var location: Bool { get { - return getBoolPreference(key: "location", defaultValue: false) - } - set { - setUserDefaults(newValue, forKey: "location") - } - } - - var deviceTokenPushNotification: String { - get { - return getStringPreference(key: "deviceTokenPushNotification", defaultValue: "") + if let value = try? keychain.get("location"), let result = Bool(value) { + return result + } + return false } set { - setUserDefaults(newValue, forKey: "deviceTokenPushNotification") + keychain["location"] = String(newValue) } } @@ -400,41 +498,109 @@ final class NCPreferences: NSObject { } func setPersonalFilesOnly(account: String, value: Bool) { - let userDefaultsKey = "personalfilesonly" + "_\(account)" - setUserDefaults(value, forKey: userDefaultsKey) + let key = "personalFilesOnly" + account + keychain[key] = String(value) } func getPersonalFilesOnly(account: String) -> Bool { - return getBoolPreference(key: "personalfilesonly", account: account, defaultValue: false) + let key = "personalFilesOnly" + account + if let value = try? keychain.get(key), let result = Bool(value) { + return result + } else { + return false + } } func setFavoriteOnTop(account: String, value: Bool) { - let userDefaultsKey = "favoriteOnTop" + "_\(account)" - setUserDefaults(value, forKey: userDefaultsKey) + let key = "favoriteOnTop" + account + keychain[key] = String(value) } func getFavoriteOnTop(account: String) -> Bool { - return getBoolPreference(key: "favoriteOnTop", account: account, defaultValue: true) + let key = "favoriteOnTop" + account + if let value = try? keychain.get(key), let result = Bool(value) { + return result + } else { + return true + } } func setDirectoryOnTop(account: String, value: Bool) { - let userDefaultsKey = "directoryOnTop" + "_\(account)" - setUserDefaults(value, forKey: userDefaultsKey) + let key = "directoryOnTop" + account + keychain[key] = String(value) } func getDirectoryOnTop(account: String) -> Bool { - return getBoolPreference(key: "directoryOnTop", account: account, defaultValue: true) + let key = "directoryOnTop" + account + if let value = try? keychain.get(key), let result = Bool(value) { + return result + } else { + return true + } } func setShowHiddenFiles(account: String, value: Bool) { - let userDefaultsKey = "showHiddenFiles" + "_\(account)" - setUserDefaults(value, forKey: userDefaultsKey) + let key = "showHiddenFiles" + account + keychain[key] = String(value) } func getShowHiddenFiles(account: String) -> Bool { - return getBoolPreference(key: "showHiddenFiles", account: account, defaultValue: false) + let key = "showHiddenFiles" + account + if let value = try? keychain.get(key), let result = Bool(value) { + return result + } else { + return false + } } + func setTitleButtonHeader(account: String, value: String?) { + let key = "titleButtonHeader" + account + keychain[key] = value + } + + func getTitleButtonHeader(account: String) -> String? { + let key = "titleButtonHeader" + account + return (try? keychain.get(key)) ?? "" + } + + @objc func getOriginalFileName(key: String) -> Bool { + migrate(key: key) + if let value = try? keychain.get(key), let result = Bool(value) { + return result + } + return false + } + + @objc func setOriginalFileName(key: String, value: Bool) { + keychain[key] = String(value) + } + + @objc func getFileNameMask(key: String) -> String { + migrate(key: key) + if let value = try? keychain.get(key) { + return value + } else { + return "" + } + } + + @objc func setFileNameMask(key: String, mask: String?) { + keychain[key] = mask + } + + @objc func getFileNameType(key: String) -> Bool { + migrate(key: key) + if let value = try? keychain.get(key), let result = Bool(value) { + return result + } else { + return false + } + } + + @objc func setFileNameType(key: String, prefix: Bool) { + keychain[key] = String(prefix) + } + // MARK: - E2EE func getEndToEndCertificate(account: String) -> String? { @@ -482,8 +648,8 @@ final class NCPreferences: NSObject { } func isEndToEndEnabled(account: String) -> Bool { - guard let capabilities = NCNetworking.shared.capabilities[account], - let certificate = getEndToEndCertificate(account: account), !certificate.isEmpty, + let capabilities = NKCapabilities.shared.getCapabilitiesBlocking(for: account) + guard let certificate = getEndToEndCertificate(account: account), !certificate.isEmpty, let publicKey = getEndToEndPublicKey(account: account), !publicKey.isEmpty, let privateKey = getEndToEndPrivateKey(account: account), !privateKey.isEmpty, let passphrase = getEndToEndPassphrase(account: account), !passphrase.isEmpty, @@ -500,62 +666,73 @@ final class NCPreferences: NSObject { setEndToEndPassphrase(account: account, passphrase: nil) } - // MARK: - PUSH NOTIFICATION + // MARK: - PUSHNOTIFICATION - func getPushNotificationPrivateKey(account: String) -> Data? { - let key = "PushPrivateKey" + account + @objc func getPushNotificationPublicKey(account: String) -> Data? { + let key = "PNPublicKey" + account return try? keychain.getData(key) } - func setPushNotificationPrivateKey(account: String, data: Data?) { - let key = "PushPrivateKey" + account + @objc func setPushNotificationPublicKey(account: String, data: Data?) { + let key = "PNPublicKey" + account keychain[data: key] = data } - func getPushNotificationPublicKey(account: String) -> Data? { - let key = "PushPublicKey" + account + @objc func getPushNotificationPrivateKey(account: String) -> Data? { + let key = "PNPrivateKey" + account return try? keychain.getData(key) } - func setPushNotificationPublicKey(account: String, data: Data?) { - let key = "PushPublicKey" + account + @objc func setPushNotificationPrivateKey(account: String, data: Data?) { + let key = "PNPrivateKey" + account keychain[data: key] = data } - func getPushNotificationSubscribingPublicKey(account: String) -> String? { - let key = "PushSubscribingPublicKey" + account + @objc func getPushNotificationSubscribingPublicKey(account: String) -> String? { + let key = "PNSubscribingPublicKey" + account return try? keychain.get(key) } - func setPushNotificationSubscribingPublicKey(account: String, publicKey: String?) { - let key = "PushSubscribingPublicKey" + account + @objc func setPushNotificationSubscribingPublicKey(account: String, publicKey: String?) { + let key = "PNSubscribingPublicKey" + account keychain[key] = publicKey } - func getPushNotificationDeviceIdentifier(account: String) -> String? { - let value = getStringPreference(key: "PushDeviceIdentifier", account: account, defaultValue: "") - return value + @objc func getPushNotificationToken(account: String) -> String? { + let key = "PNToken" + account + return try? keychain.get(key) + } + + @objc func setPushNotificationToken(account: String, token: String?) { + let key = "PNToken" + account + keychain[key] = token + } + + @objc func getPushNotificationDeviceIdentifier(account: String) -> String? { + let key = "PNDeviceIdentifier" + account + return try? keychain.get(key) } - func setPushNotificationDeviceIdentifier(account: String, deviceIdentifier: String?) { - let userDefaultsKey = "PushDeviceIdentifier" + "_\(account)" - setUserDefaults(deviceIdentifier, forKey: userDefaultsKey) + @objc func setPushNotificationDeviceIdentifier(account: String, deviceIdentifier: String?) { + let key = "PNDeviceIdentifier" + account + keychain[key] = deviceIdentifier } - func getPushNotificationDeviceIdentifierSignature(account: String) -> String? { - let key = "PushDeviceIdentifierSignature" + account + @objc func getPushNotificationDeviceIdentifierSignature(account: String) -> String? { + let key = "PNDeviceIdentifierSignature" + account return try? keychain.get(key) } - func setPushNotificationDeviceIdentifierSignature(account: String, deviceIdentifierSignature: String?) { - let key = "PushDeviceIdentifierSignature" + account + @objc func setPushNotificationDeviceIdentifierSignature(account: String, deviceIdentifierSignature: String?) { + let key = "PNDeviceIdentifierSignature" + account keychain[key] = deviceIdentifierSignature } - func clearAllKeysPushNotification(account: String) { - setPushNotificationPrivateKey(account: account, data: nil) + @objc func clearAllKeysPushNotification(account: String) { setPushNotificationPublicKey(account: account, data: nil) setPushNotificationSubscribingPublicKey(account: account, publicKey: nil) + setPushNotificationPrivateKey(account: account, data: nil) + setPushNotificationToken(account: account, token: nil) setPushNotificationDeviceIdentifier(account: account, deviceIdentifier: nil) setPushNotificationDeviceIdentifierSignature(account: account, deviceIdentifierSignature: nil) } @@ -583,9 +760,8 @@ final class NCPreferences: NSObject { // MARK: - Albums func setAutoUploadAlbumIds(account: String, albumIds: [String]) { - let userDefaultsKey = "AlbumIds" + "_\(account)" - let value = albumIds.joined(separator: ",") - setUserDefaults(value, forKey: userDefaultsKey) + let key = "AlbumIds" + account + keychain[key] = albumIds.joined(separator: ",") } func getAutoUploadAlbumIds(account: String) -> [String] { @@ -643,69 +819,7 @@ final class NCPreferences: NSObject { } } - func removeAll() { + @objc func removeAll() { try? keychain.removeAll() } - - private func setUserDefaults(_ value: Any?, forKey key: String) { - let keyPreferences = "Preferences_\(key)" - UserDefaults.standard.set(value, forKey: keyPreferences) - } - - private func getBoolPreference(key: String, account: String? = nil, defaultValue: Bool) -> Bool { - let suffix = account ?? "" - let userDefaultsKey = account != nil ? "Preferences_\(key)_\(suffix)" : "Preferences_\(key)" - let keychainKey = account != nil ? "\(key)\(suffix)" : key - - if let value = UserDefaults.standard.object(forKey: userDefaultsKey) as? Bool { - return value - } - - if let value = try? keychain.get(keychainKey), let boolValue = Bool(value) { - UserDefaults.standard.set(boolValue, forKey: userDefaultsKey) - try? keychain.remove(keychainKey) - return boolValue - } - - UserDefaults.standard.set(defaultValue, forKey: userDefaultsKey) - return defaultValue - } - - private func getStringPreference(key: String, account: String? = nil, defaultValue: String) -> String { - let suffix = account ?? "" - let userDefaultsKey = account != nil ? "Preferences_\(key)_\(suffix)" : "Preferences_\(key)" - let keychainKey = account != nil ? "\(key)\(suffix)" : key - - if let value = UserDefaults.standard.object(forKey: userDefaultsKey) as? String { - return value - } - - if let value = try? keychain.get(keychainKey) { - UserDefaults.standard.set(value, forKey: userDefaultsKey) - try? keychain.remove(keychainKey) - return value - } - - UserDefaults.standard.set(defaultValue, forKey: userDefaultsKey) - return defaultValue - } - - private func getIntPreference(key: String, account: String? = nil, defaultValue: Int) -> Int { - let suffix = account ?? "" - let userDefaultsKey = account != nil ? "Preferences_\(key)_\(suffix)" : "Preferences_\(key)" - let keychainKey = account != nil ? "\(key)\(suffix)" : key - - if let value = UserDefaults.standard.object(forKey: userDefaultsKey) as? Int { - return value - } - - if let value = try? keychain.get(keychainKey), let intValue = Int(value) { - UserDefaults.standard.set(intValue, forKey: userDefaultsKey) - try? keychain.remove(keychainKey) - return intValue - } - - UserDefaults.standard.set(defaultValue, forKey: userDefaultsKey) - return defaultValue - } } From 6add16a2c05d5af9ffec99566b2168fc7701df0d Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 6 Oct 2025 11:07:18 +0530 Subject: [PATCH 3/4] NMC 2169 - Media theming customisation changes --- iOSClient/Menu/NCMedia+Menu.swift | 1 + iOSClient/Settings/NCPreferences.swift | 82 +++++-------------- .../NCViewerMediaDetailView.swift | 1 + 3 files changed, 24 insertions(+), 60 deletions(-) diff --git a/iOSClient/Menu/NCMedia+Menu.swift b/iOSClient/Menu/NCMedia+Menu.swift index 72c6cec47b..4951c4711a 100644 --- a/iOSClient/Menu/NCMedia+Menu.swift +++ b/iOSClient/Menu/NCMedia+Menu.swift @@ -55,6 +55,7 @@ extension NCMedia { icon: utility.loadImage(named: "selectFull", colors: [NCBrandColor.shared.iconColor]), action: { _ in self.isEditMode = true + self.collectionView.reloadData() } ) ) diff --git a/iOSClient/Settings/NCPreferences.swift b/iOSClient/Settings/NCPreferences.swift index 23bc167e35..4e08a797bc 100644 --- a/iOSClient/Settings/NCPreferences.swift +++ b/iOSClient/Settings/NCPreferences.swift @@ -24,9 +24,9 @@ import Foundation import UIKit import KeychainAccess -import NextcloudKit @objc class NCKeychain: NSObject { + let keychain = Keychain(service: "com.nextcloud.keychain") var showDescription: Bool { @@ -231,20 +231,16 @@ import NextcloudKit } } - /// Stores and retrieves the current log level from the keychain. - var log: NKLogLevel { -// @objc var logLevel: Int { + @objc var logLevel: Int { get { migrate(key: "logLevel") - if let value = try? keychain.get("logLevel"), - let intValue = Int(value), - let level = NKLogLevel(rawValue: intValue) { - return level + if let value = try? keychain.get("logLevel"), let result = Int(value) { + return result } - return NKLogLevel.normal + return 1 } set { - keychain["logLevel"] = String(newValue.rawValue) + keychain["logLevel"] = String(newValue) } } @@ -277,9 +273,6 @@ import NextcloudKit @objc var privacyScreenEnabled: Bool { get { migrate(key: "privacyScreen") - if NCBrandOptions.shared.enforce_privacyScreenEnabled { - return true - } if let value = try? keychain.get("privacyScreen"), let result = Bool(value) { return result } @@ -471,25 +464,12 @@ import NextcloudKit } } - var location: Bool { - get { - if let value = try? keychain.get("location"), let result = Bool(value) { - return result - } - return false - } - set { - keychain["location"] = String(newValue) - } - } - // MARK: - - func getPassword(account: String) -> String { + @objc func getPassword(account: String) -> String { let key = "password" + account migrate(key: key) - let password = (try? keychain.get(key)) ?? "" - return password + return (try? keychain.get(key)) ?? "" } func setPassword(account: String, password: String?) { @@ -511,20 +491,7 @@ import NextcloudKit } } - func setFavoriteOnTop(account: String, value: Bool) { - let key = "favoriteOnTop" + account - keychain[key] = String(value) - } - - func getFavoriteOnTop(account: String) -> Bool { - let key = "favoriteOnTop" + account - if let value = try? keychain.get(key), let result = Bool(value) { - return result - } else { - return true - } - } - + /* OBSOLETE func setDirectoryOnTop(account: String, value: Bool) { let key = "directoryOnTop" + account keychain[key] = String(value) @@ -538,20 +505,7 @@ import NextcloudKit return true } } - - func setShowHiddenFiles(account: String, value: Bool) { - let key = "showHiddenFiles" + account - keychain[key] = String(value) - } - - func getShowHiddenFiles(account: String) -> Bool { - let key = "showHiddenFiles" + account - if let value = try? keychain.get(key), let result = Bool(value) { - return result - } else { - return false - } - } + */ func setTitleButtonHeader(account: String, value: String?) { let key = "titleButtonHeader" + account @@ -648,14 +602,12 @@ import NextcloudKit } func isEndToEndEnabled(account: String) -> Bool { - let capabilities = NKCapabilities.shared.getCapabilitiesBlocking(for: account) + let capabilities = NCCapabilities.shared.getCapabilities(account: account) guard let certificate = getEndToEndCertificate(account: account), !certificate.isEmpty, let publicKey = getEndToEndPublicKey(account: account), !publicKey.isEmpty, let privateKey = getEndToEndPrivateKey(account: account), !privateKey.isEmpty, let passphrase = getEndToEndPassphrase(account: account), !passphrase.isEmpty, - NCGlobal.shared.e2eeVersions.contains(capabilities.e2EEApiVersion) else { - return false - } + NCGlobal.shared.e2eeVersions.contains(capabilities.capabilityE2EEApiVersion) else { return false } return true } @@ -756,6 +708,16 @@ import NextcloudKit return (data, password) } + + @objc func setAccountName(account: String) { + let key = "AccountName" + keychain[key] = account + } + + @objc func getAccountName() -> String? { + let key = "AccountName" + return try? keychain.get(key) + } // MARK: - Albums diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift index 9c2c44cc3b..2615c5cad7 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift @@ -5,6 +5,7 @@ import UIKit import MapKit import NextcloudKit +import Alamofire public protocol NCViewerMediaDetailViewDelegate: AnyObject { func downloadFullResolution() From 235ed8482d64d7c5556367d93f64caa2632c103b Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 16 Dec 2025 17:23:54 +0530 Subject: [PATCH 4/4] NMC 2169 - Media theming customisation changes --- iOSClient/AppDelegate.swift | 197 ++++++- .../Data/NCManageDatabase+Metadata.swift | 141 ++--- iOSClient/Media/NCMedia.storyboard | 75 +-- iOSClient/Menu/NCMedia+Menu.swift | 340 +++++------ iOSClient/NCImageCache.swift | 82 ++- iOSClient/Settings/NCPreferences.swift | 545 ++++++++---------- .../NCViewerMediaDetailView.swift | 2 - .../NCViewerQuickLook/NCViewerQuickLook.swift | 29 +- 8 files changed, 731 insertions(+), 680 deletions(-) diff --git a/iOSClient/AppDelegate.swift b/iOSClient/AppDelegate.swift index f5698a1076..e163458460 100644 --- a/iOSClient/AppDelegate.swift +++ b/iOSClient/AppDelegate.swift @@ -17,6 +17,11 @@ import RealmSwift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { var backgroundSessionCompletionHandler: (() -> Void)? + var activeLogin: NCLogin? + var activeLoginWeb: NCLoginWeb? + var taskAutoUploadDate: Date = Date() + var orientationLock = UIInterfaceOrientationMask.all + @objc let adjust = AdjustHelper() var isUiTestingEnabled: Bool { return ProcessInfo.processInfo.arguments.contains("UI_TESTING") } @@ -31,16 +36,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD var bgTask: UIBackgroundTaskIdentifier = .invalid var pushSubscriptionTask: Task? - + var window: UIWindow? + var sceneIdentifier: String = "" + var activeViewController: UIViewController? + var account: String = "" + var urlBase: String = "" + var user: String = "" + var userId: String = "" + var password: String = "" + var timerErrorNetworking: Timer? + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { if isUiTestingEnabled { Task { await NCAccount().deleteAllAccounts() } } - UINavigationBar.appearance().tintColor = NCBrandColor.shared.customer - UIToolbar.appearance().tintColor = NCBrandColor.shared.customer - let utilityFileSystem = NCUtilityFileSystem() let utility = NCUtility() @@ -48,6 +59,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD utilityFileSystem.emptyTemporaryDirectory() utilityFileSystem.clearCacheDirectory("com.limit-point.LivePhoto") + UINavigationBar.appearance().tintColor = NCBrandColor.shared.brand + UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = NCBrandColor.shared.brand + let versionNextcloudiOS = String(format: NCBrandOptions.shared.textCopyrightNextcloudiOS, utility.getVersionBuild()) NCAppVersionManager.shared.checkAndUpdateInstallState() @@ -122,6 +136,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD if NCBrandOptions.shared.enforce_passcode_lock { NCPreferences().requestPasscodeAtStart = true } + +// if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { +// for window in windowScene.windows { +// let imageViews = window.allImageViews() +// // Do something with the imageViews +// for imageView in imageViews { +// print("Found image view: \(imageView)") +// imageView.tintColor = UITraitCollection.current.userInterfaceStyle == .dark ? .white : .black +// } +// } +// } return true } @@ -203,7 +228,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD task.setTaskCompleted(success: true) } - await backgroundSync(task: task) + guard let tblAccount = await NCManageDatabase.shared.getActiveTableAccountAsync() else { + nkLog(tag: self.global.logTagTask, emoji: .info, message: "No active account or background task already running") + return + } + + await backgroundSync(tblAccount: tblAccount, task: task) } } @@ -284,12 +314,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD guard !expired else { return } let err = await NCNetworking.shared.createFolderForAutoUpload( - serverUrlFileName: metadata.serverUrlFileName, - account: metadata.account + serverUrlFileName: meta.serverUrlFileName, + account: meta.account ) // Fail-fast: abort the whole sync on first failure if err != .success { - nkLog(tag: self.global.logTagBgSync, emoji: .error, message: "Create folder '\(metadata.serverUrlFileName)' failed: \(err.errorCode) – aborting sync") + nkLog(tag: self.global.logTagBgSync, emoji: .error, message: "Create folder '\(meta.serverUrlFileName)' failed: \(err.errorCode) – aborting sync") return } } @@ -440,7 +470,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } } - // MARK: - + // MARK: - Trust Certificate Error func trustCertificateError(host: String) { guard let activeTblAccount = NCManageDatabase.shared.getActiveTableAccount(), @@ -492,6 +522,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD NCPreferences().removeAll() + // Reset App Icon badge / File Icon badge + if #available(iOS 17.0, *) { + UNUserNotificationCenter.current().setBadgeCount(0) + } exit(0) } @@ -501,6 +535,151 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD let applicationHandle = NCApplicationHandle() return applicationHandle.applicationOpenUserActivity(userActivity) } + + // MARK: - Login + + func openLogin(selector: Int, window: UIWindow? = nil) { + UIApplication.shared.allSceneSessionDestructionExceptFirst() + + // Nextcloud standard login + if selector == NCGlobal.shared.introSignup { + if activeLogin?.view.window == nil { + if selector == NCGlobal.shared.introSignup { + let web = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLoginProvider") as? NCLoginProvider + web?.initialURLString = NCBrandOptions.shared.linkloginPreferredProviders + showLoginViewController(web) + } else { + activeLogin = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLogin") as? NCLogin + if let controller = UIApplication.shared.firstWindow?.rootViewController as? NCMainTabBarController, !controller.account.isEmpty { + let session = NCSession.shared.getSession(account: controller.account) + activeLogin?.urlBase = session.urlBase + } + showLoginViewController(activeLogin) + } + } + } else { + if activeLogin?.view.window == nil { + activeLogin = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLogin") as? NCLogin + activeLogin?.urlBase = NCBrandOptions.shared.disable_request_login_url ? NCBrandOptions.shared.loginBaseUrl : "" + showLoginViewController(activeLogin) + } + } + } + + func openLogin(viewController: UIViewController?, selector: Int, openLoginWeb: Bool) { +// openLogin(selector: NCGlobal.shared.introLogin) + // [WEBPersonalized] [AppConfig] + if NCBrandOptions.shared.use_login_web_personalized || NCBrandOptions.shared.use_AppConfig { + + if activeLoginWeb?.view.window == nil { + activeLoginWeb = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLoginWeb") as? NCLoginWeb + activeLoginWeb?.urlBase = NCBrandOptions.shared.loginBaseUrl + showLoginViewController(activeLoginWeb, contextViewController: viewController) + } + return + } + + // Nextcloud standard login + if selector == NCGlobal.shared.introSignup { + + if activeLoginWeb?.view.window == nil { + activeLoginWeb = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLoginWeb") as? NCLoginWeb + if selector == NCGlobal.shared.introSignup { + activeLoginWeb?.urlBase = NCBrandOptions.shared.linkloginPreferredProviders + } else { + activeLoginWeb?.urlBase = self.urlBase + } + showLoginViewController(activeLoginWeb, contextViewController: viewController) + } + + } else if NCBrandOptions.shared.disable_intro && NCBrandOptions.shared.disable_request_login_url { + + if activeLoginWeb?.view.window == nil { + activeLoginWeb = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLoginWeb") as? NCLoginWeb + activeLoginWeb?.urlBase = NCBrandOptions.shared.loginBaseUrl + showLoginViewController(activeLoginWeb, contextViewController: viewController) + } + + } else if openLoginWeb { + + // Used also for reinsert the account (change passwd) + if activeLoginWeb?.view.window == nil { + activeLoginWeb = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLoginWeb") as? NCLoginWeb + activeLoginWeb?.urlBase = urlBase + activeLoginWeb?.user = user + showLoginViewController(activeLoginWeb, contextViewController: viewController) + } + + } else { + + if activeLogin?.view.window == nil { + activeLogin = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLogin") as? NCLogin + showLoginViewController(activeLogin, contextViewController: viewController) + } + } + } + + func showLoginViewController(_ viewController: UIViewController?) { + guard let viewController else { return } + let navigationController = UINavigationController(rootViewController: viewController) + + navigationController.modalPresentationStyle = .fullScreen + navigationController.navigationBar.barStyle = .black + navigationController.navigationBar.tintColor = NCBrandColor.shared.customerText + navigationController.navigationBar.barTintColor = NCBrandColor.shared.customer + navigationController.navigationBar.isTranslucent = false + + if let controller = UIApplication.shared.firstWindow?.rootViewController { + if let presentedVC = controller.presentedViewController, !(presentedVC is UINavigationController) { + presentedVC.dismiss(animated: false) { + controller.present(navigationController, animated: true) + } + } else { + controller.present(navigationController, animated: true) + } + } else { + window?.rootViewController = navigationController + window?.makeKeyAndVisible() + } + } + + func showLoginViewController(_ viewController: UIViewController?, contextViewController: UIViewController?) { + + if contextViewController == nil { + if let viewController = viewController { + let navigationController = UINavigationController(rootViewController: viewController) + navigationController.navigationBar.barStyle = .black + navigationController.navigationBar.tintColor = NCBrandColor.shared.customerText + navigationController.navigationBar.barTintColor = NCBrandColor.shared.customer + navigationController.navigationBar.isTranslucent = false + window?.rootViewController = navigationController + window?.makeKeyAndVisible() + } + } else if contextViewController is UINavigationController { + if let contextViewController = contextViewController, let viewController = viewController { + (contextViewController as? UINavigationController)?.pushViewController(viewController, animated: true) + } + } else { + if let viewController = viewController, let contextViewController = contextViewController { + let navigationController = UINavigationController(rootViewController: viewController) + navigationController.modalPresentationStyle = .fullScreen + navigationController.navigationBar.barStyle = .black + navigationController.navigationBar.tintColor = NCBrandColor.shared.customerText + navigationController.navigationBar.barTintColor = NCBrandColor.shared.customer + navigationController.navigationBar.isTranslucent = false + contextViewController.present(navigationController, animated: true) { } + } + } + } + + @objc func startTimerErrorNetworking() { + timerErrorNetworking = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(checkErrorNetworking), userInfo: nil, repeats: true) + } + + @objc private func checkErrorNetworking() { + guard !account.isEmpty, NCKeychain().getPassword(account: account).isEmpty else { return } + openLogin(viewController: window?.rootViewController, selector: NCGlobal.shared.introLogin, openLoginWeb: true) + } } // MARK: - Extension diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index 292d90df27..cce5657e9d 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -156,7 +156,7 @@ extension tableMetadata { if isDocumentViewableOnly { return false } - if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKCommon.TypeClassFile.image.rawValue { + if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKTypeClassFile.image.rawValue { return true } return false @@ -165,11 +165,11 @@ extension tableMetadata { var isSavebleInCameraRoll: Bool { return (classFile == NKTypeClassFile.image.rawValue && contentType != "image/svg+xml") || classFile == NKTypeClassFile.video.rawValue } - + var isDocumentViewableOnly: Bool { - sharePermissionsCollaborationServices == NCPermissions().permissionReadShare && classFile == NKCommon.TypeClassFile.document.rawValue + sharePermissionsCollaborationServices == NCPermissions().permissionReadShare && classFile == NKTypeClassFile.document.rawValue } - + var isAudioOrVideo: Bool { return classFile == NKTypeClassFile.audio.rawValue || classFile == NKTypeClassFile.video.rawValue } @@ -195,7 +195,7 @@ extension tableMetadata { } var isCopyableInPasteboard: Bool { - !isDocumentViewableOnly && !directory + !directory } #if !EXTENSION_FILE_PROVIDER_EXTENSION @@ -204,11 +204,11 @@ extension tableMetadata { } var isCopyableMovable: Bool { - !isDocumentViewableOnly && !isDirectoryE2EE && !e2eEncrypted + !isDirectoryE2EE && !e2eEncrypted } var isModifiableWithQuickLook: Bool { - if directory || isDocumentViewableOnly || isDirectoryE2EE { + if directory || isDirectoryE2EE { return false } return isPDF || isImage @@ -239,44 +239,21 @@ extension tableMetadata { } var canSetAsAvailableOffline: Bool { -// return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted - return session.isEmpty && !isDocumentViewableOnly + return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted } var canShare: Bool { - return session.isEmpty && !isDocumentViewableOnly && !directory && !NCBrandOptions.shared.disable_openin_file - } - - var canUnsetDirectoryAsE2EE: Bool { - return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) - } - - var canOpenExternalEditor: Bool { - if isDocumentViewableOnly { - return false - } - let utility = NCUtility() - let editors = utility.editorsDirectEditing(account: account, contentType: contentType) - let isRichDocument = utility.isTypeFileRichDocument(self) - return classFile == NKCommon.TypeClassFile.document.rawValue && editors.contains(NCGlobal.shared.editorText) && ((editors.contains(NCGlobal.shared.editorOnlyoffice) || isRichDocument)) + return session.isEmpty && !directory && !NCBrandOptions.shared.disable_openin_file } - var isWaitingTransfer: Bool { - status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusWaitUpload || status == NCGlobal.shared.metadataStatusUploadError + var canSetDirectoryAsE2EE: Bool { + return directory && size == 0 && !e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) } - var isInTransfer: Bool { - status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading + var canUnsetDirectoryAsE2EE: Bool { + return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) } - var isTransferInForeground: Bool { - (status > 0 && (chunk > 0 || e2eEncrypted)) - } - - var isDownloadUpload: Bool { - status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading - } - var isDownload: Bool { status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusDownloading } @@ -417,7 +394,7 @@ extension tableMetadata { guard let capabilities = NCNetworking.shared.capabilities[account] else { return false } - if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE) { + if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE), !e2eEncrypted { return false } return !e2eEncrypted @@ -965,6 +942,34 @@ extension NCManageDatabase { .map { $0.detachedCopy() } } ?? [] } + + func getMediaMetadatas(predicate: NSPredicate, sorted: String? = nil, ascending: Bool = false) -> ThreadSafeArray? { + + do { + let realm = try Realm() + if let sorted { + var results: [tableMetadata] = [] + switch sorted {//NCPreferences().mediaSortDate { + case "date": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.date as Date) > ($1.date as Date) } + case "creationDate": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.creationDate as Date) > ($1.creationDate as Date) } + case "uploadDate": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.uploadDate as Date) > ($1.uploadDate as Date) } + default: + let results = realm.objects(tableMetadata.self).filter(predicate) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } else { + let results = realm.objects(tableMetadata.self).filter(predicate) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } + } catch let error as NSError { +// NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") + } + return nil + } func getMetadatas(predicate: NSPredicate, sortedByKeyPath: String, @@ -1208,24 +1213,12 @@ extension NCManageDatabase { counter += 1 listIdentifierRank[item.ocId] = NSNumber(value: counter) } - + return listIdentifierRank } return result ?? [:] } - @objc func clearMetadatasUpload(account: String) { - do { - let realm = try Realm() - try realm.write { - let results = realm.objects(tableMetadata.self).filter("account == %@ AND (status == %d OR status == %d)", account, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploadError) - realm.delete(results) - } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") - } - } - func getAssetLocalIdentifiersUploadedAsync() async -> [String]? { return await core.performRealmReadAsync { realm in let results = realm.objects(tableMetadata.self).filter("assetLocalIdentifier != ''") @@ -1334,41 +1327,17 @@ extension NCManageDatabase { } ?? false } - func createMetadatasFolder(assets: [PHAsset], - useSubFolder: Bool, - session: NCSession.Session, completion: @escaping ([tableMetadata]) -> Void) { - var foldersCreated: Set = [] - var metadatas: [tableMetadata] = [] - let serverUrlBase = getAccountAutoUploadDirectory(session: session) - let fileNameBase = getAccountAutoUploadFileName(account: session.account) - let predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND directory == true", session.account, serverUrlBase) - - func createMetadata(serverUrl: String, fileName: String, metadata: tableMetadata?) { - guard !foldersCreated.contains(serverUrl + "/" + fileName) else { - return - } - foldersCreated.insert(serverUrl + "/" + fileName) - - if let metadata { - metadata.status = NCGlobal.shared.metadataStatusWaitCreateFolder - metadata.sessionSelector = NCGlobal.shared.selectorUploadAutoUpload - metadata.sessionDate = Date() - metadatas.append(tableMetadata(value: metadata)) - } else { - let metadata = NCManageDatabase.shared.createMetadata(fileName: fileName, - fileNameView: fileName, - ocId: NSUUID().uuidString, - serverUrl: serverUrl, - url: "", - contentType: "httpd/unix-directory", - directory: true, - session: session, - sceneIdentifier: nil) - metadata.status = NCGlobal.shared.metadataStatusWaitCreateFolder - metadata.sessionSelector = NCGlobal.shared.selectorUploadAutoUpload - metadata.sessionDate = Date() - metadatas.append(metadata) - } + func getMetadataDirectoryAsync(serverUrl: String, account: String) async -> tableMetadata? { + guard let url = URL(string: serverUrl) else { + return nil + } + let fileName = url.lastPathComponent + var baseUrl = url.deletingLastPathComponent().absoluteString + if baseUrl.hasSuffix("/") { + baseUrl.removeLast() + } + guard let decodedBaseUrl = baseUrl.removingPercentEncoding else { + return nil } return await core.performRealmReadAsync { realm in @@ -1378,7 +1347,7 @@ extension NCManageDatabase { return object?.detachedCopy() } } - + func getMetadataDirectory(serverUrl: String, account: String) -> tableMetadata? { guard let url = URL(string: serverUrl) else { return nil diff --git a/iOSClient/Media/NCMedia.storyboard b/iOSClient/Media/NCMedia.storyboard index 517f2fdfb8..5733a680ee 100644 --- a/iOSClient/Media/NCMedia.storyboard +++ b/iOSClient/Media/NCMedia.storyboard @@ -1,10 +1,10 @@ - + - - + + @@ -18,7 +18,7 @@ - + @@ -31,78 +31,44 @@ - + - + - - - - - - - + + + + - - - - - - - - + + + - - - - - + + @@ -111,4 +77,9 @@ + + + + + diff --git a/iOSClient/Menu/NCMedia+Menu.swift b/iOSClient/Menu/NCMedia+Menu.swift index 4951c4711a..495fd11d09 100644 --- a/iOSClient/Menu/NCMedia+Menu.swift +++ b/iOSClient/Menu/NCMedia+Menu.swift @@ -43,175 +43,175 @@ extension NCMedia { func toggleMenu() { - var actions: [NCMenuAction] = [] - - defer { presentMenu(with: actions) } - - if !isEditMode { - if let metadatas = self.metadatas, !metadatas.isEmpty { - actions.append( - NCMenuAction( - title: NSLocalizedString("_select_", comment: ""), - icon: utility.loadImage(named: "selectFull", colors: [NCBrandColor.shared.iconColor]), - action: { _ in - self.isEditMode = true - self.collectionView.reloadData() - } - ) - ) - } - - actions.append(.seperator(order: 0)) - - actions.append( - NCMenuAction( - title: NSLocalizedString("_media_viewimage_show_", comment: ""), - icon: utility.loadImage(named: showOnlyImages ? "nocamera" : "file_photo_menu", colors: [NCBrandColor.shared.iconColor]), - selected: showOnlyImages, - on: true, - action: { _ in - self.showOnlyImages = true - self.showOnlyVideos = false - self.loadDataSource() - } - ) - ) - - actions.append( - NCMenuAction( - title: NSLocalizedString("_media_viewvideo_show_", comment: ""), - icon: utility.loadImage(named: showOnlyVideos ? "videono" : "videoyes", colors: [NCBrandColor.shared.iconColor]), - selected: showOnlyVideos, - on: true, - action: { _ in - self.showOnlyImages = false - self.showOnlyVideos = true - self.loadDataSource() - } - ) - ) - - actions.append( - NCMenuAction( - title: NSLocalizedString("_media_show_all_", comment: ""), - icon: utility.loadImage(named: "photo.on.rectangle.angled", colors: [NCBrandColor.shared.iconColor]), - selected: !showOnlyImages && !showOnlyVideos, - on: true, - action: { _ in - self.showOnlyImages = false - self.showOnlyVideos = false - self.loadDataSource() - } - ) - ) - - actions.append(.seperator(order: 0)) - - actions.append( - NCMenuAction( - title: NSLocalizedString("_select_media_folder_", comment: ""), - icon: utility.loadImage(named: "folder", colors: [NCBrandColor.shared.iconColor]), - action: { _ in - if let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController, - let viewController = navigationController.topViewController as? NCSelect { - - viewController.delegate = self - viewController.typeOfCommandView = .select - viewController.type = "mediaFolder" - - self.present(navigationController, animated: true, completion: nil) - } - } - ) - ) - - actions.append( - NCMenuAction( - title: NSLocalizedString("_media_by_modified_date_", comment: ""), - icon: utility.loadImage(named: "sortFileNameAZ", colors: [NCBrandColor.shared.iconColor]), - selected: NCKeychain().mediaSortDate == "date", - on: true, - action: { _ in - NCKeychain().mediaSortDate = "date" - self.loadDataSource() - } - ) - ) - - actions.append( - NCMenuAction( - title: NSLocalizedString("_media_by_created_date_", comment: ""), - icon: utility.loadImage(named: "sortFileNameAZ", colors: [NCBrandColor.shared.iconColor]), - selected: NCKeychain().mediaSortDate == "creationDate", - on: true, - action: { _ in - NCKeychain().mediaSortDate = "creationDate" - self.loadDataSource() - } - ) - ) - - actions.append( - NCMenuAction( - title: NSLocalizedString("_media_by_upload_date_", comment: ""), - icon: utility.loadImage(named: "sortFileNameAZ", colors: [NCBrandColor.shared.iconColor]), - selected: NCKeychain().mediaSortDate == "uploadDate", - on: true, - action: { _ in - NCKeychain().mediaSortDate = "uploadDate" - self.loadDataSource() - } - ) - ) - - } else { - - // - // CANCEL - // - actions.append( - NCMenuAction( - title: NSLocalizedString("_cancel_", comment: ""), - icon: utility.loadImage(named: "xmark", colors: [NCBrandColor.shared.iconColor]), - action: { _ in self.tapSelect() } - ) - ) - - if fileSelect.count != dataSource.metadatas.count { - actions.append(.selectAllAction(action: selectAll)) - } - guard !fileSelect.isEmpty else { return } - - actions.append(.seperator(order: 0)) - - let selectedMetadatas = fileSelect.compactMap(NCManageDatabase.shared.getMetadataFromOcId) - - // - // OPEN IN - // - actions.append(.openInAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: tapSelect)) - - // - // SAVE TO PHOTO GALLERY - // - actions.append(.saveMediaAction(selectedMediaMetadatas: selectedMetadatas, controller: self.controller, completion: tapSelect)) - - // - // COPY - MOVE - // - actions.append(.moveOrCopyAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: tapSelect)) - - // - // COPY - // - actions.append(.copyAction(fileSelect: fileSelect, controller: self.controller, completion: tapSelect)) - - // - // DELETE - // can't delete from cache because is needed for NCMedia view, and if locked can't delete from server either. - if !selectedMetadatas.contains(where: { $0.lock && $0.lockOwner != session.userId }) { - actions.append(.deleteAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: tapSelect)) - } - } +// var actions: [NCMenuAction] = [] +// +// defer { presentMenu(with: actions) } +// +// if !isEditMode { +// if let metadatas = self.metadatas, !metadatas.isEmpty { +// actions.append( +// NCMenuAction( +// title: NSLocalizedString("_select_", comment: ""), +// icon: utility.loadImage(named: "selectFull", colors: [NCBrandColor.shared.iconImageColor]), +// action: { _ in +// self.isEditMode = true +// self.collectionView.reloadData() +// } +// ) +// ) +// } +// +// actions.append(.seperator(order: 0)) +// +// actions.append( +// NCMenuAction( +// title: NSLocalizedString("_media_viewimage_show_", comment: ""), +// icon: utility.loadImage(named: showOnlyImages ? "nocamera" : "file_photo_menu", colors: [NCBrandColor.shared.iconImageColor]), +// selected: showOnlyImages, +// on: true, +// action: { _ in +// self.showOnlyImages = true +// self.showOnlyVideos = false +// self.loadDataSource() +// } +// ) +// ) +// +// actions.append( +// NCMenuAction( +// title: NSLocalizedString("_media_viewvideo_show_", comment: ""), +// icon: utility.loadImage(named: showOnlyVideos ? "videono" : "videoyes", colors: [NCBrandColor.shared.iconImageColor]), +// selected: showOnlyVideos, +// on: true, +// action: { _ in +// self.showOnlyImages = false +// self.showOnlyVideos = true +// self.loadDataSource() +// } +// ) +// ) +// +// actions.append( +// NCMenuAction( +// title: NSLocalizedString("_media_show_all_", comment: ""), +// icon: utility.loadImage(named: "photo.on.rectangle.angled", colors: [NCBrandColor.shared.iconImageColor]), +// selected: !showOnlyImages && !showOnlyVideos, +// on: true, +// action: { _ in +// self.showOnlyImages = false +// self.showOnlyVideos = false +// self.loadDataSource() +// } +// ) +// ) +// +// actions.append(.seperator(order: 0)) +// +// actions.append( +// NCMenuAction( +// title: NSLocalizedString("_select_media_folder_", comment: ""), +// icon: utility.loadImage(named: "folder", colors: [NCBrandColor.shared.iconImageColor]), +// action: { _ in +// if let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController, +// let viewController = navigationController.topViewController as? NCSelect { +// +// viewController.delegate = self +// viewController.typeOfCommandView = .select +// viewController.type = "mediaFolder" +// +// self.present(navigationController, animated: true, completion: nil) +// } +// } +// ) +// ) +// +// actions.append( +// NCMenuAction( +// title: NSLocalizedString("_media_by_modified_date_", comment: ""), +// icon: utility.loadImage(named: "sortFileNameAZ", colors: [NCBrandColor.shared.iconImageColor]), +// selected: NCKeychain().mediaSortDate == "date", +// on: true, +// action: { _ in +// NCKeychain().mediaSortDate = "date" +// self.loadDataSource() +// } +// ) +// ) +// +// actions.append( +// NCMenuAction( +// title: NSLocalizedString("_media_by_created_date_", comment: ""), +// icon: utility.loadImage(named: "sortFileNameAZ", colors: [NCBrandColor.shared.iconImageColor]), +// selected: NCKeychain().mediaSortDate == "creationDate", +// on: true, +// action: { _ in +// NCKeychain().mediaSortDate = "creationDate" +// self.loadDataSource() +// } +// ) +// ) +// +// actions.append( +// NCMenuAction( +// title: NSLocalizedString("_media_by_upload_date_", comment: ""), +// icon: utility.loadImage(named: "sortFileNameAZ", colors: [NCBrandColor.shared.iconImageColor]), +// selected: NCKeychain().mediaSortDate == "uploadDate", +// on: true, +// action: { _ in +// NCKeychain().mediaSortDate = "uploadDate" +// self.loadDataSource() +// } +// ) +// ) +// +// } else { +// +// // +// // CANCEL +// // +// actions.append( +// NCMenuAction( +// title: NSLocalizedString("_cancel_", comment: ""), +// icon: utility.loadImage(named: "xmark", colors: [NCBrandColor.shared.iconImageColor]), +// action: { _ in self.tapSelect() } +// ) +// ) +// +// if fileSelect.count != dataSource.metadatas.count { +// actions.append(.selectAllAction(action: selectAll)) +// } +// guard !fileSelect.isEmpty else { return } +// +// actions.append(.seperator(order: 0)) +// +// let selectedMetadatas = fileSelect.compactMap(NCManageDatabase.shared.getMetadataFromOcId) +// +// // +// // OPEN IN +// // +// actions.append(.openInAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: tapSelect)) +// +// // +// // SAVE TO PHOTO GALLERY +// // +// actions.append(.saveMediaAction(selectedMediaMetadatas: selectedMetadatas, controller: self.controller, completion: tapSelect)) +// +// // +// // COPY - MOVE +// // +// actions.append(.moveOrCopyAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: tapSelect)) +// +// // +// // COPY +// // +// actions.append(.copyAction(fileSelect: fileSelect, controller: self.controller, completion: tapSelect)) +// +// // +// // DELETE +// // can't delete from cache because is needed for NCMedia view, and if locked can't delete from server either. +// if !selectedMetadatas.contains(where: { $0.lock && $0.lockOwner != session.userId }) { +// actions.append(.deleteAction(selectedMetadatas: selectedMetadatas, controller: self.controller, completion: tapSelect)) +// } +// } } } diff --git a/iOSClient/NCImageCache.swift b/iOSClient/NCImageCache.swift index e9dcccd855..a30d081221 100644 --- a/iOSClient/NCImageCache.swift +++ b/iOSClient/NCImageCache.swift @@ -19,8 +19,6 @@ final class NCImageCache: @unchecked Sendable { private let allowExtensions = [NCGlobal.shared.previewExt256] private var brandElementColor: UIColor? - private var observerToken: NSObjectProtocol? - public var countLimit: Int = 2000 lazy var cache: LRUCache = { return LRUCache(countLimit: countLimit) @@ -29,10 +27,7 @@ final class NCImageCache: @unchecked Sendable { public var isLoadingCache: Bool = false public var controller: UITabBarController? - var createMediaCacheInProgress: Bool = false - let showAllPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == '\(NKCommon.TypeClassFile.image.rawValue)' OR classFile == '\(NKCommon.TypeClassFile.video.rawValue)') AND NOT (session CONTAINS[c] 'upload')" - let showBothPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == '\(NKCommon.TypeClassFile.image.rawValue)' OR classFile == '\(NKCommon.TypeClassFile.video.rawValue)') AND NOT (session CONTAINS[c] 'upload') AND NOT (livePhotoFile != '' AND classFile == '\(NKCommon.TypeClassFile.video.rawValue)')" - let showOnlyPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload') AND NOT (livePhotoFile != '' AND classFile == '\(NKCommon.TypeClassFile.video.rawValue)')" + let showBothPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == '\(NKTypeClassFile.image.rawValue)' OR classFile == '\(NKTypeClassFile.video.rawValue)') AND NOT (session CONTAINS[c] 'upload') AND NOT (livePhotoFile != '' AND classFile == '\(NKTypeClassFile.video.rawValue)')" init() { observerToken = NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: nil) { _ in @@ -41,7 +36,8 @@ final class NCImageCache: @unchecked Sendable { } NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in - self.cache.removeAll() + self.cache.removeAllValues() +// self.cache.removeAll() self.cache = LRUCache(countLimit: self.countLimit) } @@ -68,10 +64,10 @@ final class NCImageCache: @unchecked Sendable { self.isLoadingCache = true self.database.filterAndNormalizeLivePhotos(from: metadatas) { metadatas in autoreleasepool { - self.cache.removeAll() + self.cache.removeAllValues() for metadata in metadatas { guard !isAppInBackground else { - self.cache.removeAll() + self.cache.removeAllValues() break } if let image = self.utility.getImage(ocId: metadata.ocId, @@ -93,27 +89,16 @@ final class NCImageCache: @unchecked Sendable { } deinit { - if let token = observerToken { - NotificationCenter.default.removeObserver(token) - } + NotificationCenter.default.removeObserver(self, name: LRUCacheMemoryWarningNotification, object: nil) } - func calculateMaxImages(percentage: Double, imageSizeKB: Double) -> Int { - let totalRamBytes = Double(ProcessInfo.processInfo.physicalMemory) - let cacheSizeBytes = totalRamBytes * (percentage / 100.0) - let imageSizeBytes = imageSizeKB * 1024 - let maxImages = Int(cacheSizeBytes / imageSizeBytes) - - return maxImages - } - func getMediaMetadatas(account: String, predicate: NSPredicate? = nil) -> ThreadSafeArray? { guard let tableAccount = NCManageDatabase.shared.getTableAccount(predicate: NSPredicate(format: "account == %@", account)) else { return nil } let startServerUrl = NCUtilityFileSystem().getHomeServer(urlBase: tableAccount.urlBase, userId: tableAccount.userId) + tableAccount.mediaPath let predicateBoth = NSPredicate(format: showBothPredicateMediaString, account, startServerUrl) return NCManageDatabase.shared.getMediaMetadatas(predicate: predicate ?? predicateBoth, sorted: "date") } - + func allowExtensions(ext: String) -> Bool { return allowExtensions.contains(ext) } @@ -150,7 +135,8 @@ final class NCImageCache: @unchecked Sendable { } func removeAll() { - cache.removeAll() +// cache.removeAll() + self.cache.removeAllValues() } // MARK: - MEDIA - @@ -179,78 +165,82 @@ final class NCImageCache: @unchecked Sendable { // MARK: - func getImageFile(colors: [UIColor] = [NCBrandColor.shared.iconImageColor2]) -> UIImage { - return utility.loadImage(named: "doc", colors: colors) + return UIImage(named: "file")!.image(color: colors.first!, size: 24) } - func getImageShared(colors: [UIColor] = NCBrandColor.shared.iconImageMultiColors) -> UIImage { - return utility.loadImage(named: "person.fill.badge.plus", colors: colors) + func getImageShared(colors: [UIColor] = [NCBrandColor.shared.iconSystemGrayColor]) -> UIImage { + return utility.loadImage(named: "share", colors: colors, size: 24) } - func getImageCanShare(colors: [UIColor] = NCBrandColor.shared.iconImageMultiColors) -> UIImage { - return utility.loadImage(named: "person.fill.badge.plus", colors: colors) + func getImageCanShare(colors: [UIColor] = [NCBrandColor.shared.iconSystemGrayColor]) -> UIImage { + return utility.loadImage(named: "share", colors: colors, size: 24) } - func getImageShareByLink(colors: [UIColor] = [NCBrandColor.shared.iconImageColor]) -> UIImage { - return utility.loadImage(named: "link", colors: colors) + func getImageShareByLink(colors: [UIColor] = [NCBrandColor.shared.iconSystemGrayColor]) -> UIImage { + return utility.loadImage(named: "share", colors: colors, size: 24) } + func getImageSharedWithMe(colors: [UIColor] = [NCBrandColor.shared.iconSystemGrayColor]) -> UIImage { + return utility.loadImage(named: "cloudUpload", colors: [NCBrandColor.shared.nmcIconSharedWithMe], size: 24) + } + func getImageFavorite(colors: [UIColor] = [NCBrandColor.shared.yellowFavorite]) -> UIImage { - return utility.loadImage(named: "star.fill", colors: colors) + return utility.loadImage(named: "star.fill", colors: colors, size: 24) } func getImageOfflineFlag(colors: [UIColor] = [.systemGreen]) -> UIImage { - return utility.loadImage(named: "arrow.down.circle.fill", colors: colors) + return utility.loadImage(named: "arrow.down.circle.fill", colors: colors, size: 24) } func getImageLocal(colors: [UIColor] = [.systemGreen]) -> UIImage { - return utility.loadImage(named: "checkmark.circle.fill", colors: colors) + return utility.loadImage(named: "checkmark.circle.fill", colors: colors, size: 24) } - func getImageCheckedYes(colors: [UIColor] = [NCBrandColor.shared.iconImageColor2]) -> UIImage { - return utility.loadImage(named: "checkmark.circle.fill", colors: colors) + func getImageCheckedYes(colors: [UIColor] = [NCBrandColor.shared.iconImageColor]) -> UIImage { + return UIImage(named: "checkedYes")! } func getImageCheckedNo(colors: [UIColor] = [NCBrandColor.shared.iconImageColor]) -> UIImage { - return utility.loadImage(named: "circle", colors: colors) + return utility.loadImage(named: "circle", colors: colors, size: 24) } func getImageButtonMore(colors: [UIColor] = [NCBrandColor.shared.iconImageColor]) -> UIImage { - return utility.loadImage(named: "ellipsis", colors: colors) + return UIImage(named: "more")!.image(color: .systemGray, size: 24) } func getImageButtonStop(colors: [UIColor] = [NCBrandColor.shared.iconImageColor]) -> UIImage { - return utility.loadImage(named: "stop.circle", colors: colors) + return utility.loadImage(named: "stop.circle", colors: colors, size: 24) } func getImageButtonMoreLock(colors: [UIColor] = [NCBrandColor.shared.iconImageColor]) -> UIImage { - return utility.loadImage(named: "lock.fill", colors: colors) + return utility.loadImage(named: "lock.fill", colors: colors, size: 24) } func getFolder(account: String) -> UIImage { - return UIImage(named: "folder")!.image(color: NCBrandColor.shared.getElement(account: account)) + return UIImage(named: "folder")! } func getFolderEncrypted(account: String) -> UIImage { - return UIImage(named: "folderEncrypted")!.image(color: NCBrandColor.shared.getElement(account: account)) + return UIImage(named: "folderEncrypted")! } func getFolderSharedWithMe(account: String) -> UIImage { - return UIImage(named: "folder_shared_with_me")!.image(color: NCBrandColor.shared.getElement(account: account)) + return UIImage(named: "folder_shared_with_me")! } func getFolderPublic(account: String) -> UIImage { - return UIImage(named: "folder_public")!.image(color: NCBrandColor.shared.getElement(account: account)) + return UIImage(named: "folder_public")! } func getFolderGroup(account: String) -> UIImage { - return UIImage(named: "folder_group")!.image(color: NCBrandColor.shared.getElement(account: account)) + return UIImage(named: "folder_group")! } func getFolderExternal(account: String) -> UIImage { - return UIImage(named: "folder_external")!.image(color: NCBrandColor.shared.getElement(account: account)) + return UIImage(named: "folder_external")! } func getFolderAutomaticUpload(account: String) -> UIImage { - return UIImage(named: "folderAutomaticUpload")!.image(color: NCBrandColor.shared.getElement(account: account)) + return UIImage(named: "folderAutomaticUpload")! } } diff --git a/iOSClient/Settings/NCPreferences.swift b/iOSClient/Settings/NCPreferences.swift index 4e08a797bc..96ce2c79ee 100644 --- a/iOSClient/Settings/NCPreferences.swift +++ b/iOSClient/Settings/NCPreferences.swift @@ -1,72 +1,44 @@ -// -// NCKeychain.swift -// Nextcloud -// -// Created by Marino Faggiana on 23/10/23. -// Copyright © 2023 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2023 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import Foundation import UIKit import KeychainAccess +import NextcloudKit -@objc class NCKeychain: NSObject { - +final class NCPreferences: NSObject { let keychain = Keychain(service: "com.nextcloud.keychain") var showDescription: Bool { get { - if let value = try? keychain.get("showDescription"), let result = Bool(value) { - return result - } - return true + return getBoolPreference(key: "showDescription", defaultValue: true) } set { - keychain["showDescription"] = String(newValue) + setUserDefaults(newValue, forKey: "showDescription") } } var showRecommendedFiles: Bool { get { - if let value = try? keychain.get("showRecommendedFiles"), let result = Bool(value) { - return result - } - return true + return getBoolPreference(key: "showRecommendedFiles", defaultValue: true) } set { - keychain["showRecommendedFiles"] = String(newValue) + setUserDefaults(newValue, forKey: "showRecommendedFiles") } } var typeFilterScanDocument: NCGlobal.TypeFilterScanDocument { get { - if let rawValue = try? keychain.get("ScanDocumentTypeFilter"), let value = NCGlobal.TypeFilterScanDocument(rawValue: rawValue) { - return value - } else { - return .original - } + let rawValue = getStringPreference(key: "ScanDocumentTypeFilter", defaultValue: NCGlobal.TypeFilterScanDocument.original.rawValue) + return NCGlobal.TypeFilterScanDocument(rawValue: rawValue) ?? .original } set { - keychain["ScanDocumentTypeFilter"] = newValue.rawValue + setUserDefaults(newValue.rawValue, forKey: "ScanDocumentTypeFilter") } } - @objc var passcode: String? { + var passcode: String? { get { migrate(key: "passcodeBlock") if let value = try? keychain.get("passcodeBlock"), !value.isEmpty { @@ -79,7 +51,7 @@ import KeychainAccess } } - @objc var resetAppCounterFail: Bool { + var resetAppCounterFail: Bool { get { if let value = try? keychain.get("resetAppCounterFail"), let result = Bool(value) { return result @@ -115,7 +87,7 @@ import KeychainAccess } } - @objc var requestPasscodeAtStart: Bool { + var requestPasscodeAtStart: Bool { get { let keychainOLD = Keychain(service: "Crypto Cloud") if let value = keychainOLD["notPasscodeAtStart"], !value.isEmpty { @@ -138,7 +110,7 @@ import KeychainAccess } } - @objc var touchFaceID: Bool { + var touchFaceID: Bool { get { migrate(key: "enableTouchFaceID") if let value = try? keychain.get("enableTouchFaceID"), let result = Bool(value) { @@ -155,321 +127,264 @@ import KeychainAccess return passcode != nil && requestPasscodeAtStart } - @objc var incrementalNumber: String { - migrate(key: "incrementalnumber") + var incrementalNumber: String { var incrementalString = String(format: "%04ld", 0) - if let value = try? keychain.get("incrementalnumber"), var result = Int(value) { - result += 1 - incrementalString = String(format: "%04ld", result) + let value = getStringPreference(key: "incrementalnumber", defaultValue: incrementalString) + if var intValue = Int(value) { + intValue += 1 + incrementalString = String(format: "%04ld", intValue) } - keychain["incrementalnumber"] = incrementalString + setUserDefaults(incrementalString, forKey: "incrementalnumber") return incrementalString } - @objc var showHiddenFiles: Bool { + var formatCompatibility: Bool { get { - migrate(key: "showHiddenFiles") - if let value = try? keychain.get("showHiddenFiles"), let result = Bool(value) { - return result - } - return false - } - set { - keychain["showHiddenFiles"] = String(newValue) - } - } - - @objc var formatCompatibility: Bool { - get { - migrate(key: "formatCompatibility") - if let value = try? keychain.get("formatCompatibility"), let result = Bool(value) { - return result - } - return true + return getBoolPreference(key: "formatCompatibility", defaultValue: true) } set { - keychain["formatCompatibility"] = String(newValue) + setUserDefaults(newValue, forKey: "formatCompatibility") } } - @objc var disableFilesApp: Bool { + var disableFilesApp: Bool { get { - migrate(key: "disablefilesapp") - if let value = try? keychain.get("disablefilesapp"), let result = Bool(value) { - return result - } - return false + return getBoolPreference(key: "disablefilesapp", defaultValue: false) } set { - keychain["disablefilesapp"] = String(newValue) + setUserDefaults(newValue, forKey: "disablefilesapp") } } - @objc var livePhoto: Bool { + var livePhoto: Bool { get { - migrate(key: "livePhoto") - if let value = try? keychain.get("livePhoto"), let result = Bool(value) { - return result - } - return true + return getBoolPreference(key: "livePhoto", defaultValue: true) } set { - keychain["livePhoto"] = String(newValue) + setUserDefaults(newValue, forKey: "livePhoto") } } - @objc var disableCrashservice: Bool { + var disableCrashservice: Bool { get { - migrate(key: "crashservice") - if let value = try? keychain.get("crashservice"), let result = Bool(value) { - return result - } - return false + return getBoolPreference(key: "crashservice", defaultValue: false) } set { - keychain["crashservice"] = String(newValue) + setUserDefaults(newValue, forKey: "crashservice") } } - @objc var logLevel: Int { + /// Stores and retrieves the current log level from the keychain. + var log: NKLogLevel { get { - migrate(key: "logLevel") - if let value = try? keychain.get("logLevel"), let result = Int(value) { - return result - } - return 1 + let value = getIntPreference(key: "logLevel", defaultValue: NKLogLevel.normal.rawValue) + return NKLogLevel(rawValue: value) ?? NKLogLevel.normal } set { - keychain["logLevel"] = String(newValue) + setUserDefaults(newValue.rawValue, forKey: "logLevel") } } - @objc var accountRequest: Bool { + var accountRequest: Bool { get { - migrate(key: "accountRequest") - if let value = try? keychain.get("accountRequest"), let result = Bool(value) { - return result - } - return false + return getBoolPreference(key: "accountRequest", defaultValue: false) } set { - keychain["accountRequest"] = String(newValue) + setUserDefaults(newValue, forKey: "accountRequest") } } - @objc var removePhotoCameraRoll: Bool { + var removePhotoCameraRoll: Bool { get { - migrate(key: "removePhotoCameraRoll") - if let value = try? keychain.get("removePhotoCameraRoll"), let result = Bool(value) { - return result - } - return false + return getBoolPreference(key: "removePhotoCameraRoll", defaultValue: false) } set { - keychain["removePhotoCameraRoll"] = String(newValue) + setUserDefaults(newValue, forKey: "removePhotoCameraRoll") } } - @objc var privacyScreenEnabled: Bool { + var privacyScreenEnabled: Bool { get { - migrate(key: "privacyScreen") - if let value = try? keychain.get("privacyScreen"), let result = Bool(value) { - return result + if NCBrandOptions.shared.enforce_privacyScreenEnabled { + return true } - return false + return getBoolPreference(key: "privacyScreen", defaultValue: false) } set { - keychain["privacyScreen"] = String(newValue) + setUserDefaults(newValue, forKey: "privacyScreen") } } - @objc var cleanUpDay: Int { + var cleanUpDay: Int { get { - migrate(key: "cleanUpDay") - if let value = try? keychain.get("cleanUpDay"), let result = Int(value) { - return result - } - return NCBrandOptions.shared.cleanUpDay + let value = getIntPreference(key: "cleanUpDay", defaultValue: NCBrandOptions.shared.cleanUpDay) + return value } set { - keychain["cleanUpDay"] = String(newValue) + setUserDefaults(newValue, forKey: "cleanUpDay") } } var mediaColumnCount: Int { get { - if let value = try? keychain.get("mediaColumnCount"), let result = Int(value) { - return result - } - return 3 + let value = getIntPreference(key: "mediaColumnCount", defaultValue: 3) + return value } set { - keychain["mediaColumnCount"] = String(newValue) + setUserDefaults(newValue, forKey: "mediaColumnCount") } } var mediaTypeLayout: String { get { - if let value = try? keychain.get("mediaTypeLayout") { - return value - } - return NCGlobal.shared.mediaLayoutRatio + let value = getStringPreference(key: "mediaTypeLayout", defaultValue: NCGlobal.shared.mediaLayoutRatio) + return value } set { - keychain["mediaTypeLayout"] = String(newValue) + setUserDefaults(newValue, forKey: "mediaTypeLayout") } } var mediaSortDate: String { get { - migrate(key: "mediaSortDate") - if let value = try? keychain.get("mediaSortDate") { - return value - } - return "date" + let value = getStringPreference(key: "mediaSortDate", defaultValue: "date") + return value } set { - keychain["mediaSortDate"] = newValue + setUserDefaults(newValue, forKey: "mediaSortDate") } } var textRecognitionStatus: Bool { get { - migrate(key: "textRecognitionStatus") - if let value = try? keychain.get("textRecognitionStatus"), let result = Bool(value) { - return result - } - return false + return getBoolPreference(key: "textRecognitionStatus", defaultValue: false) } set { - keychain["textRecognitionStatus"] = String(newValue) + setUserDefaults(newValue, forKey: "textRecognitionStatus") } } var deleteAllScanImages: Bool { get { - migrate(key: "deleteAllScanImages") - if let value = try? keychain.get("deleteAllScanImages"), let result = Bool(value) { - return result - } - return false + return getBoolPreference(key: "deleteAllScanImages", defaultValue: false) } set { - keychain["deleteAllScanImages"] = String(newValue) + setUserDefaults(newValue, forKey: "deleteAllScanImages") } } var qualityScanDocument: Double { get { - migrate(key: "qualityScanDocument") - if let value = try? keychain.get("qualityScanDocument"), let result = Double(value) { - return result - } - return 2 + let value = getIntPreference(key: "qualityScanDocument", defaultValue: 2) + return Double(value) } set { - keychain["qualityScanDocument"] = String(newValue) + setUserDefaults(newValue, forKey: "qualityScanDocument") } } var appearanceAutomatic: Bool { get { - if let value = try? keychain.get("appearanceAutomatic"), let result = Bool(value) { - return result - } - return true + let value = getBoolPreference(key: "appearanceAutomatic", defaultValue: true) + return value } set { - keychain["appearanceAutomatic"] = String(newValue) + setUserDefaults(newValue, forKey: "appearanceAutomatic") } } var appearanceInterfaceStyle: UIUserInterfaceStyle { get { - if let value = try? keychain.get("appearanceInterfaceStyle") { - if value == "light" { - return .light - } else { - return .dark - } + let value = getStringPreference(key: "appearanceInterfaceStyle", defaultValue: "light") + if value == "light" { + return .light + } else { + return .dark } - return .light } set { if newValue == .light { - keychain["appearanceInterfaceStyle"] = "light" + setUserDefaults("light", forKey: "appearanceInterfaceStyle") } else { - keychain["appearanceInterfaceStyle"] = "dark" + setUserDefaults("dark", forKey: "appearanceInterfaceStyle") } } } var screenAwakeMode: AwakeMode { get { - if let value = try? keychain.get("screenAwakeMode") { - if value == "off" { - return .off - } else if value == "on" { - return .on - } else { - return .whileCharging - } + let value = getStringPreference(key: "screenAwakeMode", defaultValue: "off") + if value == "off" { + return .off + } else if value == "on" { + return .on + } else { + return .whileCharging } - return .off } set { if newValue == .off { - keychain["screenAwakeMode"] = "off" + setUserDefaults("off", forKey: "screenAwakeMode") } else if newValue == .on { - keychain["screenAwakeMode"] = "on" + setUserDefaults("on", forKey: "screenAwakeMode") } else { - keychain["screenAwakeMode"] = "whileCharging" + setUserDefaults("whileCharging", forKey: "screenAwakeMode") } } } var fileNameType: Bool { get { - if let value = try? keychain.get("fileNameType"), let result = Bool(value) { - return result - } - return false + return getBoolPreference(key: "fileNameType", defaultValue: false) } set { - keychain["fileNameType"] = String(newValue) + setUserDefaults(newValue, forKey: "fileNameType") } } var fileNameOriginal: Bool { get { - if let value = try? keychain.get("fileNameOriginal"), let result = Bool(value) { - return result - } - return false + return getBoolPreference(key: "fileNameOriginal", defaultValue: false) } set { - keychain["fileNameOriginal"] = String(newValue) + setUserDefaults(newValue, forKey: "fileNameOriginal") } } var fileNameMask: String { get { - if let value = try? keychain.get("fileNameMask") { - return value - } - return "" + return getStringPreference(key: "fileNameMask", defaultValue: "") + } + set { + setUserDefaults(newValue, forKey: "fileNameMask") + } + } + + var location: Bool { + get { + return getBoolPreference(key: "location", defaultValue: false) + } + set { + setUserDefaults(newValue, forKey: "location") + } + } + + var deviceTokenPushNotification: String { + get { + return getStringPreference(key: "deviceTokenPushNotification", defaultValue: "") } set { - keychain["fileNameMask"] = String(newValue) + setUserDefaults(newValue, forKey: "deviceTokenPushNotification") } } // MARK: - - @objc func getPassword(account: String) -> String { + func getPassword(account: String) -> String { let key = "password" + account migrate(key: key) - return (try? keychain.get(key)) ?? "" + let password = (try? keychain.get(key)) ?? "" + return password } func setPassword(account: String, password: String?) { @@ -478,83 +393,41 @@ import KeychainAccess } func setPersonalFilesOnly(account: String, value: Bool) { - let key = "personalFilesOnly" + account - keychain[key] = String(value) + let userDefaultsKey = "personalfilesonly" + "_\(account)" + setUserDefaults(value, forKey: userDefaultsKey) } func getPersonalFilesOnly(account: String) -> Bool { - let key = "personalFilesOnly" + account - if let value = try? keychain.get(key), let result = Bool(value) { - return result - } else { - return false - } - } - - /* OBSOLETE - func setDirectoryOnTop(account: String, value: Bool) { - let key = "directoryOnTop" + account - keychain[key] = String(value) - } - - func getDirectoryOnTop(account: String) -> Bool { - let key = "directoryOnTop" + account - if let value = try? keychain.get(key), let result = Bool(value) { - return result - } else { - return true - } + return getBoolPreference(key: "personalfilesonly", account: account, defaultValue: false) } - */ - func setTitleButtonHeader(account: String, value: String?) { - let key = "titleButtonHeader" + account - keychain[key] = value + func setFavoriteOnTop(account: String, value: Bool) { + let userDefaultsKey = "favoriteOnTop" + "_\(account)" + setUserDefaults(value, forKey: userDefaultsKey) } - func getTitleButtonHeader(account: String) -> String? { - let key = "titleButtonHeader" + account - return (try? keychain.get(key)) ?? "" - } - - @objc func getOriginalFileName(key: String) -> Bool { - migrate(key: key) - if let value = try? keychain.get(key), let result = Bool(value) { - return result - } - return false + func getFavoriteOnTop(account: String) -> Bool { + return getBoolPreference(key: "favoriteOnTop", account: account, defaultValue: true) } - @objc func setOriginalFileName(key: String, value: Bool) { - keychain[key] = String(value) + func setDirectoryOnTop(account: String, value: Bool) { + let userDefaultsKey = "directoryOnTop" + "_\(account)" + setUserDefaults(value, forKey: userDefaultsKey) } - @objc func getFileNameMask(key: String) -> String { - migrate(key: key) - if let value = try? keychain.get(key) { - return value - } else { - return "" - } + func getDirectoryOnTop(account: String) -> Bool { + return getBoolPreference(key: "directoryOnTop", account: account, defaultValue: true) } - @objc func setFileNameMask(key: String, mask: String?) { - keychain[key] = mask + func setShowHiddenFiles(account: String, value: Bool) { + let userDefaultsKey = "showHiddenFiles" + "_\(account)" + setUserDefaults(value, forKey: userDefaultsKey) } - @objc func getFileNameType(key: String) -> Bool { - migrate(key: key) - if let value = try? keychain.get(key), let result = Bool(value) { - return result - } else { - return false - } + func getShowHiddenFiles(account: String) -> Bool { + return getBoolPreference(key: "showHiddenFiles", account: account, defaultValue: false) } - @objc func setFileNameType(key: String, prefix: Bool) { - keychain[key] = String(prefix) - } - // MARK: - E2EE func getEndToEndCertificate(account: String) -> String? { @@ -602,12 +475,14 @@ import KeychainAccess } func isEndToEndEnabled(account: String) -> Bool { - let capabilities = NCCapabilities.shared.getCapabilities(account: account) - guard let certificate = getEndToEndCertificate(account: account), !certificate.isEmpty, + guard let capabilities = NCNetworking.shared.capabilities[account], + let certificate = getEndToEndCertificate(account: account), !certificate.isEmpty, let publicKey = getEndToEndPublicKey(account: account), !publicKey.isEmpty, let privateKey = getEndToEndPrivateKey(account: account), !privateKey.isEmpty, let passphrase = getEndToEndPassphrase(account: account), !passphrase.isEmpty, - NCGlobal.shared.e2eeVersions.contains(capabilities.capabilityE2EEApiVersion) else { return false } + NCGlobal.shared.e2eeVersions.contains(capabilities.e2EEApiVersion) else { + return false + } return true } @@ -618,73 +493,62 @@ import KeychainAccess setEndToEndPassphrase(account: account, passphrase: nil) } - // MARK: - PUSHNOTIFICATION + // MARK: - PUSH NOTIFICATION - @objc func getPushNotificationPublicKey(account: String) -> Data? { - let key = "PNPublicKey" + account + func getPushNotificationPrivateKey(account: String) -> Data? { + let key = "PushPrivateKey" + account return try? keychain.getData(key) } - @objc func setPushNotificationPublicKey(account: String, data: Data?) { - let key = "PNPublicKey" + account + func setPushNotificationPrivateKey(account: String, data: Data?) { + let key = "PushPrivateKey" + account keychain[data: key] = data } - @objc func getPushNotificationPrivateKey(account: String) -> Data? { - let key = "PNPrivateKey" + account + func getPushNotificationPublicKey(account: String) -> Data? { + let key = "PushPublicKey" + account return try? keychain.getData(key) } - @objc func setPushNotificationPrivateKey(account: String, data: Data?) { - let key = "PNPrivateKey" + account + func setPushNotificationPublicKey(account: String, data: Data?) { + let key = "PushPublicKey" + account keychain[data: key] = data } - @objc func getPushNotificationSubscribingPublicKey(account: String) -> String? { - let key = "PNSubscribingPublicKey" + account + func getPushNotificationSubscribingPublicKey(account: String) -> String? { + let key = "PushSubscribingPublicKey" + account return try? keychain.get(key) } - @objc func setPushNotificationSubscribingPublicKey(account: String, publicKey: String?) { - let key = "PNSubscribingPublicKey" + account + func setPushNotificationSubscribingPublicKey(account: String, publicKey: String?) { + let key = "PushSubscribingPublicKey" + account keychain[key] = publicKey } - @objc func getPushNotificationToken(account: String) -> String? { - let key = "PNToken" + account - return try? keychain.get(key) - } - - @objc func setPushNotificationToken(account: String, token: String?) { - let key = "PNToken" + account - keychain[key] = token - } - - @objc func getPushNotificationDeviceIdentifier(account: String) -> String? { - let key = "PNDeviceIdentifier" + account - return try? keychain.get(key) + func getPushNotificationDeviceIdentifier(account: String) -> String? { + let value = getStringPreference(key: "PushDeviceIdentifier", account: account, defaultValue: "") + return value } - @objc func setPushNotificationDeviceIdentifier(account: String, deviceIdentifier: String?) { - let key = "PNDeviceIdentifier" + account - keychain[key] = deviceIdentifier + func setPushNotificationDeviceIdentifier(account: String, deviceIdentifier: String?) { + let userDefaultsKey = "PushDeviceIdentifier" + "_\(account)" + setUserDefaults(deviceIdentifier, forKey: userDefaultsKey) } - @objc func getPushNotificationDeviceIdentifierSignature(account: String) -> String? { - let key = "PNDeviceIdentifierSignature" + account + func getPushNotificationDeviceIdentifierSignature(account: String) -> String? { + let key = "PushDeviceIdentifierSignature" + account return try? keychain.get(key) } - @objc func setPushNotificationDeviceIdentifierSignature(account: String, deviceIdentifierSignature: String?) { - let key = "PNDeviceIdentifierSignature" + account + func setPushNotificationDeviceIdentifierSignature(account: String, deviceIdentifierSignature: String?) { + let key = "PushDeviceIdentifierSignature" + account keychain[key] = deviceIdentifierSignature } - @objc func clearAllKeysPushNotification(account: String) { + func clearAllKeysPushNotification(account: String) { + setPushNotificationPrivateKey(account: account, data: nil) setPushNotificationPublicKey(account: account, data: nil) setPushNotificationSubscribingPublicKey(account: account, publicKey: nil) - setPushNotificationPrivateKey(account: account, data: nil) - setPushNotificationToken(account: account, token: nil) setPushNotificationDeviceIdentifier(account: account, deviceIdentifier: nil) setPushNotificationDeviceIdentifierSignature(account: account, deviceIdentifierSignature: nil) } @@ -708,22 +572,13 @@ import KeychainAccess return (data, password) } - - @objc func setAccountName(account: String) { - let key = "AccountName" - keychain[key] = account - } - - @objc func getAccountName() -> String? { - let key = "AccountName" - return try? keychain.get(key) - } // MARK: - Albums func setAutoUploadAlbumIds(account: String, albumIds: [String]) { - let key = "AlbumIds" + account - keychain[key] = albumIds.joined(separator: ",") + let userDefaultsKey = "AlbumIds" + "_\(account)" + let value = albumIds.joined(separator: ",") + setUserDefaults(value, forKey: userDefaultsKey) } func getAutoUploadAlbumIds(account: String) -> [String] { @@ -781,7 +636,69 @@ import KeychainAccess } } - @objc func removeAll() { + func removeAll() { try? keychain.removeAll() } + + private func setUserDefaults(_ value: Any?, forKey key: String) { + let keyPreferences = "Preferences_\(key)" + UserDefaults.standard.set(value, forKey: keyPreferences) + } + + private func getBoolPreference(key: String, account: String? = nil, defaultValue: Bool) -> Bool { + let suffix = account ?? "" + let userDefaultsKey = account != nil ? "Preferences_\(key)_\(suffix)" : "Preferences_\(key)" + let keychainKey = account != nil ? "\(key)\(suffix)" : key + + if let value = UserDefaults.standard.object(forKey: userDefaultsKey) as? Bool { + return value + } + + if let value = try? keychain.get(keychainKey), let boolValue = Bool(value) { + UserDefaults.standard.set(boolValue, forKey: userDefaultsKey) + try? keychain.remove(keychainKey) + return boolValue + } + + UserDefaults.standard.set(defaultValue, forKey: userDefaultsKey) + return defaultValue + } + + private func getStringPreference(key: String, account: String? = nil, defaultValue: String) -> String { + let suffix = account ?? "" + let userDefaultsKey = account != nil ? "Preferences_\(key)_\(suffix)" : "Preferences_\(key)" + let keychainKey = account != nil ? "\(key)\(suffix)" : key + + if let value = UserDefaults.standard.object(forKey: userDefaultsKey) as? String { + return value + } + + if let value = try? keychain.get(keychainKey) { + UserDefaults.standard.set(value, forKey: userDefaultsKey) + try? keychain.remove(keychainKey) + return value + } + + UserDefaults.standard.set(defaultValue, forKey: userDefaultsKey) + return defaultValue + } + + private func getIntPreference(key: String, account: String? = nil, defaultValue: Int) -> Int { + let suffix = account ?? "" + let userDefaultsKey = account != nil ? "Preferences_\(key)_\(suffix)" : "Preferences_\(key)" + let keychainKey = account != nil ? "\(key)\(suffix)" : key + + if let value = UserDefaults.standard.object(forKey: userDefaultsKey) as? Int { + return value + } + + if let value = try? keychain.get(keychainKey), let intValue = Int(value) { + UserDefaults.standard.set(intValue, forKey: userDefaultsKey) + try? keychain.remove(keychainKey) + return intValue + } + + UserDefaults.standard.set(defaultValue, forKey: userDefaultsKey) + return defaultValue + } } diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift index 2615c5cad7..0cf1201651 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift @@ -5,7 +5,6 @@ import UIKit import MapKit import NextcloudKit -import Alamofire public protocol NCViewerMediaDetailViewDelegate: AnyObject { func downloadFullResolution() @@ -189,7 +188,6 @@ class NCViewerMediaDetailView: UIView { } if metadata.isImage && !utilityFileSystem.fileProviderStorageExists(metadata) && metadata.session.isEmpty { - downloadImageButton.tintColor = NCBrandColor.shared.brand downloadImageButton.setTitle(NSLocalizedString("_try_download_full_resolution_", comment: ""), for: .normal) downloadImageLabel.text = NSLocalizedString("_full_resolution_image_info_", comment: "") downloadImageButtonContainer.isHidden = false diff --git a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift index 2386e611ca..17b6852762 100644 --- a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift +++ b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift @@ -158,7 +158,7 @@ private var hasChangesQuickLook: Bool = false toolbarConfig.optionButtonFontSize = 16 toolbarConfig.optionButtonFontSizeForPad = 21 toolbarConfig.backgroundColor = .systemGray6 - toolbarConfig.foregroundColor = NCBrandColor.shared.customer + toolbarConfig.foregroundColor = .systemBlue var viewConfig = CropViewConfig() viewConfig.cropMaskVisualEffectType = .none @@ -198,6 +198,16 @@ extension NCViewerQuickLook: QLPreviewControllerDataSource, QLPreviewControllerD return isEditingEnabled ? .createCopy : .disabled // File is in private storage, so .updateContents is not possible and will still act as .createCopy. } +// func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode { +// // Check if the editing mode allows updating the original contents +// if isEditingEnabled { +// hasChangesQuickLook = true // Mark changes if editing is enabled +// return .createCopy // Allows editing and overwriting the original file +// } +// +// return .disabled // Disable editing if not enabled +// } + fileprivate func saveModifiedFile(override: Bool) { guard let metadata = self.metadata else { return } let session = NCSession.shared.getSession(account: metadata.account) @@ -244,6 +254,23 @@ extension NCViewerQuickLook: QLPreviewControllerDataSource, QLPreviewControllerD guard utilityFileSystem.copyFile(atPath: modifiedContentsURL.path, toPath: url.path) else { return } hasChangesQuickLook = true } + +// func previewController(_ controller: QLPreviewController, didSaveEditedContentsOf previewItem: QLPreviewItem, at modifiedURL: URL) { +// // This method is called if the user saves a *new* copy of the edited file. +// print("Content was saved to a new URL: \(modifiedURL)") +// hasChangesQuickLook = true +// // You might need to update your internal fileURL reference here if you want to use the new file. +// } +// +// func previewController(_ controller: QLPreviewController, didUpdateContentsOf previewItem: QLPreviewItem) { +// // Check if the file contents have actually been updated +// if let fileURL = previewItem.previewItemURL { +// // Custom logic to check if contents have been modified +// print("File contents updated at: \(fileURL)") +// hasChangesQuickLook = true // Mark as changed +// } +// } + } extension NCViewerQuickLook: CropViewControllerDelegate {