From e3e98f977d549f2046fc81c58ce70defd281fb5a 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/8] 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 e37f4f8cc2..1ffad71f20 100644 --- a/iOSClient/AppDelegate.swift +++ b/iOSClient/AppDelegate.swift @@ -37,6 +37,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 011ddbf681..6b4b19e80a 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -1303,17 +1303,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 @@ -1323,7 +1347,7 @@ extension NCManageDatabase { return object?.detachedCopy() } } - + func getMetadataDirectory(serverUrl: String, account: String) -> tableMetadata? { guard let url = URL(string: serverUrl) else { return nil @@ -1343,6 +1367,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 eb40357d0f..b8aaeb9a22 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 387fb10bfd..8a133deeb9 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 37dabd4a1a..4de2c83325 100644 --- a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift +++ b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift @@ -159,7 +159,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 399a02877a9806699a67dec1f8c572a7268de92a Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 14 Apr 2025 11:39:24 +0530 Subject: [PATCH 2/8] NMC 2169 - Media theming changes --- Nextcloud.xcodeproj/project.pbxproj | 23 + .../Data/NCManageDatabase+Metadata.swift | 143 ++++- iOSClient/Menu/NCMedia+Menu.swift | 65 ++- iOSClient/NCImageCache.swift | 8 + iOSClient/Settings/NCPreferences.swift | 507 +++++++++++------- 5 files changed, 513 insertions(+), 233 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index a3788d3efc..ac9f66bc5f 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -85,6 +85,11 @@ AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; }; AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* Shareable.swift */; }; CB3666201AF7550816B5CD6A /* NCContextMenuComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8932E90EC4278026D86CCCC9 /* NCContextMenuComment.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 */; }; F31165022F9674A1009A1E37 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = F31165012F9674A1009A1E37 /* AppIcon.icon */; }; @@ -1270,6 +1275,9 @@ AFCE353827E5DE0400FEA6C2 /* Shareable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shareable.swift; sourceTree = ""; }; B4C7A5B36D1ED178FB6B76CB /* NCContextMenuPlayerTracks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuPlayerTracks.swift; sourceTree = ""; }; BB7697C94BA14450A0867940 /* NCContextMenuProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuProfile.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 = ""; }; @@ -2064,6 +2072,18 @@ AF93471127E2341B002537EE /* NCContextMenuShare.swift */, F75D19E225EFE09000D74598 /* NCContextMenuTrash.swift */, F710D2012405826100A6033D /* NCContextMenuViewer.swift */, + F77A697C250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift */, + F78C6FDD296D677300C952C3 /* NCContextMenu.swift */, + B5C980192DACD56C0041B146 /* NCMedia+Menu.swift */, + 3704EB2923D5A58400455C5B /* NCMenu.storyboard */, + 371B5A2D23D0B04500FAFAE9 /* NCMenu.swift */, + AF935066276B84E700BD078F /* NCMenu+FloatingPanel.swift */, + AF68326927BE65A90010BF0B /* NCMenuAction.swift */, + F7FAFD3928BFA947000777FE /* NCNotification+Menu.swift */, + AF93471127E2341B002537EE /* NCShare+Menu.swift */, + F75D19E225EFE09000D74598 /* NCTrash+Menu.swift */, + F710D2012405826100A6033D /* NCViewerContextMenu.swift */, + 8491B1CC273BBA82001C8C5B /* UIViewController+Menu.swift */, ); path = Menu; sourceTree = ""; @@ -3223,6 +3243,7 @@ F7EC9CB921185F2000F1C5CE /* Media */ = { isa = PBXGroup; children = ( + B5C980172DACD51C0041B146 /* NCMediaCommandView.xib */, F720B5B72507B9A5008C94E5 /* Cell */, F7501C302212E57400FB1415 /* NCMedia.storyboard */, F7501C312212E57400FB1415 /* NCMedia.swift */, @@ -4056,6 +4077,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 */, @@ -4886,6 +4908,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 */, F3DDFE1E2F1F8EC600A784C8 /* ChatInputField.swift in Sources */, diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index 6b4b19e80a..a5118810e8 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -151,9 +151,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 @@ -180,7 +207,7 @@ extension tableMetadata { } var isCopyableInPasteboard: Bool { - !directory + !isDocumentViewableOnly && !directory } #if !EXTENSION_FILE_PROVIDER_EXTENSION @@ -189,11 +216,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 @@ -224,18 +251,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 { @@ -344,6 +423,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: Primitive properties and lists of primitive values (for example `shareType`) @@ -1195,6 +1285,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 b8aaeb9a22..f8859ca1da 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 8a133deeb9..9c6488b1c6 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,6 +648,7 @@ final class NCPreferences: NSObject { } func isEndToEndEnabled(account: String) -> Bool { + 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, @@ -498,62 +665,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) } @@ -581,9 +759,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] { @@ -641,69 +818,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 cffd2e5d3960c499901f12e602dfd2493c1fb7fb Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 6 Oct 2025 11:07:18 +0530 Subject: [PATCH 3/8] NMC 2169 - Media theming customisation changes --- iOSClient/Menu/NCMedia+Menu.swift | 1 + iOSClient/Settings/NCPreferences.swift | 83 +++++-------------- .../NCViewerMediaDetailView.swift | 1 + 3 files changed, 25 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 9c6488b1c6..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,13 +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 else { - return false - } + let passphrase = getEndToEndPassphrase(account: account), !passphrase.isEmpty, + NCGlobal.shared.e2eeVersions.contains(capabilities.capabilityE2EEApiVersion) else { return false } return true } @@ -755,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 9423bd76513058f4890ad4039bcb0fc8ae08d665 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 16 Dec 2025 17:23:54 +0530 Subject: [PATCH 4/8] NMC 2169 - Media theming customisation changes --- iOSClient/AppDelegate.swift | 381 +++++++++++- .../Data/NCManageDatabase+Metadata.swift | 141 ++--- iOSClient/Media/NCMedia.storyboard | 75 +-- iOSClient/Menu/NCMedia+Menu.swift | 340 +++++------ iOSClient/NCImageCache.swift | 84 ++- iOSClient/Settings/NCPreferences.swift | 545 ++++++++---------- .../NCViewerMediaDetailView.swift | 2 - .../NCViewerQuickLook/NCViewerQuickLook.swift | 29 +- 8 files changed, 924 insertions(+), 673 deletions(-) diff --git a/iOSClient/AppDelegate.swift b/iOSClient/AppDelegate.swift index 1ffad71f20..996667d51a 100644 --- a/iOSClient/AppDelegate.swift +++ b/iOSClient/AppDelegate.swift @@ -16,6 +16,11 @@ import RealmSwift 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") } @@ -30,16 +35,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() @@ -47,6 +58,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() @@ -117,6 +131,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 } @@ -148,6 +173,203 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } + // MARK: - Background Task + + /* + @discussion Schedule a refresh task request to ask that the system launch your app briefly so that you can download data and keep your app's contents up-to-date. The system will fulfill this request intelligently based on system conditions and app usage. + */ + func scheduleAppRefresh() { + let request = BGAppRefreshTaskRequest(identifier: global.refreshTask) + + request.earliestBeginDate = Date(timeIntervalSinceNow: 60) // Refresh after 60 seconds. + + do { + try BGTaskScheduler.shared.submit(request) + } catch { + nkLog(tag: self.global.logTagTask, emoji: .error, message: "Refresh task failed to submit request: \(error)") + } + } + + /* + @discussion Schedule a processing task request to ask that the system launch your app when conditions are favorable for battery life to handle deferrable, longer-running processing, such as syncing, database maintenance, or similar tasks. The system will attempt to fulfill this request to the best of its ability within the next two days as long as the user has used your app within the past week. + */ + func scheduleAppProcessing() { + let request = BGProcessingTaskRequest(identifier: global.processingTask) + + request.earliestBeginDate = Date(timeIntervalSinceNow: 5 * 60) // Refresh after 5 minutes. + request.requiresNetworkConnectivity = false + request.requiresExternalPower = false + + do { + try BGTaskScheduler.shared.submit(request) + } catch { + nkLog(tag: self.global.logTagTask, emoji: .error, message: "Processing task failed to submit request: \(error)") + } + } + + func handleAppRefresh(_ task: BGAppRefreshTask) { + nkLog(tag: self.global.logTagTask, emoji: .start, message: "Start refresh task") + guard NCManageDatabase.shared.openRealmBackground() else { + nkLog(tag: self.global.logTagTask, emoji: .error, message: "Failed to open Realm in background") + task.setTaskCompleted(success: false) + return + } + + // Schedule next refresh + scheduleAppRefresh() + + Task { + defer { + task.setTaskCompleted(success: true) + } + + 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) + } + } + + func handleProcessingTask(_ task: BGProcessingTask) { + nkLog(tag: self.global.logTagTask, emoji: .start, message: "Start processing task") + guard NCManageDatabase.shared.openRealmBackground() else { + nkLog(tag: self.global.logTagTask, emoji: .error, message: "Failed to open Realm in background") + task.setTaskCompleted(success: false) + return + } + var expired = false + task.expirationHandler = { + expired = true + } + + // Schedule next processing task + scheduleAppProcessing() + + Task { + defer { + task.setTaskCompleted(success: true) + } + + // If possible, cleaning every week + if NCPreferences().cleaningWeek() { + // BGTask expiration flag + nkLog(tag: self.global.logTagBgSync, emoji: .start, message: "Start cleaning week") + let tblAccounts = await NCManageDatabase.shared.getAllTableAccountAsync() + for tblAccount in tblAccounts { + await NCManageDatabase.shared.cleanTablesOcIds(account: tblAccount.account, userId: tblAccount.userId, urlBase: tblAccount.urlBase) + guard !expired else { return } + } + await NCUtilityFileSystem().cleanUpAsync() + + NCPreferences().setDoneCleaningWeek() + nkLog(tag: self.global.logTagBgSync, emoji: .stop, message: "Stop cleaning week") + } else { + await backgroundSync(task: task) + } + } + } + + func backgroundSync(task: BGTask? = nil) async { + defer { + // Update badge safely at the end of the background sync + Task { @MainActor in + do { + let count = await NCManageDatabase.shared.getMetadatasInWaitingCountAsync() + try await UNUserNotificationCenter.current().setBadgeCount(count) + } catch { } + } + } + + // BGTask expiration flag + var expired = false + task?.expirationHandler = { + expired = true + } + + // Discover new items for Auto Upload + let numAutoUpload = await NCAutoUpload.shared.initAutoUpload() + nkLog(tag: self.global.logTagBgSync, emoji: .start, message: "Auto upload found \(numAutoUpload) new items") + guard !expired else { return } + + // Fetch METADATAS + let metadatas = await NCManageDatabase.shared.getMetadataProcess() + guard !metadatas.isEmpty, !expired else { + return + } + + // Create all pending Auto Upload folders (fail-fast) + let pendingCreateFolders = metadatas.lazy.filter { + $0.status == self.global.metadataStatusWaitCreateFolder && + $0.sessionSelector == self.global.selectorUploadAutoUpload + } + + for metadata in pendingCreateFolders { + guard !expired else { return } + + let err = await NCNetworking.shared.createFolderForAutoUpload( + 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 '\(meta.serverUrlFileName)' failed: \(err.errorCode) – aborting sync") + return + } + } + + // Capacity computation + let downloading = metadatas.lazy.filter { $0.status == self.global.metadataStatusDownloading }.count + let uploading = metadatas.lazy.filter { $0.status == self.global.metadataStatusUploading }.count + let availableProcess = max(0, NCBrandOptions.shared.numMaximumProcess - (downloading + uploading)) + + // Start Auto Uploads + let metadatasToUpload = Array( + metadatas.lazy.filter { + $0.status == self.global.metadataStatusWaitUpload && + $0.sessionSelector == self.global.selectorUploadAutoUpload && + $0.chunk == 0 + } + .prefix(availableProcess) + ) + + let cameraRoll = NCCameraRoll() + + for metadata in metadatasToUpload { + guard !expired else { return } + + // File exists? skip it + let existsResult = await NCNetworking.shared.fileExists(serverUrlFileName: metadata.serverUrlFileName, account: metadata.account) + if existsResult == .success { + // File exists → delete from local metadata and skip + await NCManageDatabase.shared.deleteMetadataAsync(id: metadata.ocId) + continue + } else if existsResult.errorCode == 404 { + // 404 Not Found → directory does not exist + // Proceed + } else { + // Any other error (423 locked, 401 auth, 403 forbidden, 5xx, etc.) + continue + } + + // Expand seed into concrete metadatas (e.g., Live Photo pair) + let extracted = await cameraRoll.extractCameraRoll(from: metadata) + guard !expired else { return } + + for metadata in extracted { + // Sequential await keeps ordering and simplifies backpressure + let err = await NCNetworking.shared.uploadFileInBackground(metadata: metadata.detachedCopy()) + if err == .success { + nkLog(tag: self.global.logTagBgSync, message: "In queued upload \(metadata.fileName) -> \(metadata.serverUrl)") + } else { + nkLog(tag: self.global.logTagBgSync, emoji: .error, message: "Upload failed \(metadata.fileName) -> \(metadata.serverUrl) [\(err.errorDescription)]") + } + guard !expired else { return } + } + } + } + // MARK: - Background Networking Session func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { @@ -248,7 +470,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } } - // MARK: - + // MARK: - Trust Certificate Error func trustCertificateError(host: String) { guard let activeTblAccount = NCManageDatabase.shared.getActiveTableAccount(), @@ -300,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) } @@ -308,6 +534,151 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { return false } + + // 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 a5118810e8..cf292bd5c9 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -168,7 +168,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 @@ -177,11 +177,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 } @@ -207,7 +207,7 @@ extension tableMetadata { } var isCopyableInPasteboard: Bool { - !isDocumentViewableOnly && !directory + !directory } #if !EXTENSION_FILE_PROVIDER_EXTENSION @@ -216,11 +216,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 @@ -251,44 +251,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 } @@ -428,7 +405,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 @@ -1008,6 +985,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, @@ -1298,24 +1303,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 != ''") @@ -1424,41 +1417,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 @@ -1468,7 +1437,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 f8859ca1da..7d07aa5a31 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 - @@ -213,31 +199,35 @@ 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(color: UIColor) -> UIImage? { @@ -252,43 +242,51 @@ final class NCImageCache: @unchecked Sendable { return UIImage(systemName: "circle", withConfiguration: config) } + 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, 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 4de2c83325..7eddf30c48 100644 --- a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift +++ b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift @@ -159,7 +159,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 @@ -199,6 +199,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) @@ -245,6 +255,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 { From ebbb53edba9d33635592fab8f548c021e5458b5f 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 5/8] NMC 2169 - Media theming customisation changes --- iOSClient/AppDelegate.swift | 3 + .../Data/NCManageDatabase+Metadata.swift | 48 ++++++++++---- iOSClient/Media/NCMedia.storyboard | 64 +++++++++++++------ iOSClient/NCImageCache.swift | 13 ++++ .../NCViewerMediaDetailView.swift | 1 + .../NCViewerQuickLook/NCViewerQuickLook.swift | 2 +- 6 files changed, 99 insertions(+), 32 deletions(-) diff --git a/iOSClient/AppDelegate.swift b/iOSClient/AppDelegate.swift index 996667d51a..c5c965d9c8 100644 --- a/iOSClient/AppDelegate.swift +++ b/iOSClient/AppDelegate.swift @@ -51,6 +51,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 cf292bd5c9..e6f7c4a559 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -1417,17 +1417,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 @@ -1437,7 +1461,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 5733a680ee..42e2f62040 100644 --- a/iOSClient/Media/NCMedia.storyboard +++ b/iOSClient/Media/NCMedia.storyboard @@ -4,7 +4,6 @@ - @@ -18,7 +17,7 @@ - + @@ -31,41 +30,73 @@ - + - + + + + - - - - + + + + + + + + + - - - + + + + @@ -77,9 +108,4 @@ - - - - - diff --git a/iOSClient/NCImageCache.swift b/iOSClient/NCImageCache.swift index 7d07aa5a31..ba4daa6fad 100644 --- a/iOSClient/NCImageCache.swift +++ b/iOSClient/NCImageCache.swift @@ -99,6 +99,19 @@ final class NCImageCache: @unchecked Sendable { return NCManageDatabase.shared.getMediaMetadatas(predicate: predicate ?? predicateBoth, sorted: "date") } + 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/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 7eddf30c48..ed90feeb17 100644 --- a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift +++ b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift @@ -159,7 +159,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 699064083c001c4de5e4146928ed6eb193f74b80 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 14 Apr 2025 11:39:24 +0530 Subject: [PATCH 6/8] NMC 2169 - Media theming changes --- .../Data/NCManageDatabase+Metadata.swift | 52 +- iOSClient/Settings/NCPreferences.swift | 506 +++++++++++------- 2 files changed, 357 insertions(+), 201 deletions(-) diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index e6f7c4a559..af5d3a2b12 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -177,6 +177,10 @@ 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 + } var isDocumentViewableOnly: Bool { sharePermissionsCollaborationServices == NCPermissions().permissionReadShare && classFile == NKTypeClassFile.document.rawValue @@ -207,7 +211,7 @@ extension tableMetadata { } var isCopyableInPasteboard: Bool { - !directory + !isDocumentViewableOnly && !directory } #if !EXTENSION_FILE_PROVIDER_EXTENSION @@ -216,11 +220,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 @@ -251,7 +255,8 @@ extension tableMetadata { } var canSetAsAvailableOffline: Bool { - return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted +// return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted + return session.isEmpty && !isDocumentViewableOnly } var canShare: Bool { @@ -266,6 +271,32 @@ extension tableMetadata { return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) } + var canOpenExternalEditor: Bool { + if isDocumentViewableOnly { + return false + } + let utility = NCUtility() + let editors = utility.editorsDirectEditing(account: account, contentType: contentType) + let isRichDocument = utility.isTypeFileRichDocument(self) + return classFile == NKCommon.TypeClassFile.document.rawValue && editors.contains(NCGlobal.shared.editorText) && ((editors.contains(NCGlobal.shared.editorOnlyoffice) || isRichDocument)) + } + + var isWaitingTransfer: Bool { + status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusWaitUpload || status == NCGlobal.shared.metadataStatusUploadError + } + + var isInTransfer: Bool { + status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading + } + + var isTransferInForeground: Bool { + (status > 0 && (chunk > 0 || e2eEncrypted)) + } + + var isDownloadUpload: Bool { + status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading + } + var isDownload: Bool { status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusDownloading } @@ -1303,12 +1334,23 @@ 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 != ''") diff --git a/iOSClient/Settings/NCPreferences.swift b/iOSClient/Settings/NCPreferences.swift index 96ce2c79ee..e2d07c720b 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) } } @@ -257,124 +335,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) } } @@ -393,41 +491,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? { @@ -493,62 +659,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) } @@ -576,9 +753,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] { @@ -636,69 +812,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 cdc0b2ce96bbb066a1c90f07290a856e9afc7001 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 6 Oct 2025 11:07:18 +0530 Subject: [PATCH 7/8] NMC 2169 - Media theming customisation changes --- iOSClient/Settings/NCPreferences.swift | 76 +++++-------------- .../NCViewerMediaDetailView.swift | 1 + 2 files changed, 21 insertions(+), 56 deletions(-) diff --git a/iOSClient/Settings/NCPreferences.swift b/iOSClient/Settings/NCPreferences.swift index e2d07c720b..81b18e71ad 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 } @@ -464,25 +457,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?) { @@ -504,20 +484,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) @@ -531,20 +498,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 @@ -749,6 +703,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 6baebbc553a538b178f0ae99083d06d91cf26413 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 16 Dec 2025 17:23:54 +0530 Subject: [PATCH 8/8] NMC 2169 - Media theming customisation changes --- iOSClient/AppDelegate.swift | 3 - .../Data/NCManageDatabase+Metadata.swift | 99 +--- iOSClient/Media/NCMedia.storyboard | 64 +-- iOSClient/Settings/NCPreferences.swift | 512 ++++++++---------- .../NCViewerMediaDetailView.swift | 2 - .../NCViewerQuickLook/NCViewerQuickLook.swift | 2 +- 6 files changed, 253 insertions(+), 429 deletions(-) diff --git a/iOSClient/AppDelegate.swift b/iOSClient/AppDelegate.swift index c5c965d9c8..996667d51a 100644 --- a/iOSClient/AppDelegate.swift +++ b/iOSClient/AppDelegate.swift @@ -51,9 +51,6 @@ 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 af5d3a2b12..f85ab01a9b 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -177,10 +177,6 @@ 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 - } var isDocumentViewableOnly: Bool { sharePermissionsCollaborationServices == NCPermissions().permissionReadShare && classFile == NKTypeClassFile.document.rawValue @@ -211,7 +207,7 @@ extension tableMetadata { } var isCopyableInPasteboard: Bool { - !isDocumentViewableOnly && !directory + !directory } #if !EXTENSION_FILE_PROVIDER_EXTENSION @@ -220,11 +216,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 @@ -255,8 +251,7 @@ extension tableMetadata { } var canSetAsAvailableOffline: Bool { -// return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted - return session.isEmpty && !isDocumentViewableOnly + return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted } var canShare: Bool { @@ -271,32 +266,6 @@ extension tableMetadata { return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) } - var canOpenExternalEditor: Bool { - if isDocumentViewableOnly { - return false - } - let utility = NCUtility() - let editors = utility.editorsDirectEditing(account: account, contentType: contentType) - let isRichDocument = utility.isTypeFileRichDocument(self) - return classFile == NKCommon.TypeClassFile.document.rawValue && editors.contains(NCGlobal.shared.editorText) && ((editors.contains(NCGlobal.shared.editorOnlyoffice) || isRichDocument)) - } - - var isWaitingTransfer: Bool { - status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusWaitUpload || status == NCGlobal.shared.metadataStatusUploadError - } - - var isInTransfer: Bool { - status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading - } - - var isTransferInForeground: Bool { - (status > 0 && (chunk > 0 || e2eEncrypted)) - } - - var isDownloadUpload: Bool { - status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading - } - var isDownload: Bool { status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusDownloading } @@ -1339,18 +1308,6 @@ extension NCManageDatabase { 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 != ''") @@ -1459,41 +1416,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 @@ -1503,7 +1436,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 42e2f62040..5733a680ee 100644 --- a/iOSClient/Media/NCMedia.storyboard +++ b/iOSClient/Media/NCMedia.storyboard @@ -4,6 +4,7 @@ + @@ -17,7 +18,7 @@ - + @@ -30,73 +31,41 @@ - + - + - - - - - - - + + + + - - - - - - - - + + + - @@ -108,4 +77,9 @@ + + + + + diff --git a/iOSClient/Settings/NCPreferences.swift b/iOSClient/Settings/NCPreferences.swift index 81b18e71ad..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,144 +127,101 @@ 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 + return getBoolPreference(key: "formatCompatibility", defaultValue: true) } set { - keychain["showHiddenFiles"] = String(newValue) + setUserDefaults(newValue, forKey: "formatCompatibility") } } - @objc var formatCompatibility: Bool { + var disableFilesApp: Bool { get { - migrate(key: "formatCompatibility") - if let value = try? keychain.get("formatCompatibility"), let result = Bool(value) { - return result - } - return true + return getBoolPreference(key: "disablefilesapp", defaultValue: false) } set { - keychain["formatCompatibility"] = String(newValue) + setUserDefaults(newValue, forKey: "disablefilesapp") } } - @objc var disableFilesApp: Bool { + var livePhoto: Bool { get { - migrate(key: "disablefilesapp") - if let value = try? keychain.get("disablefilesapp"), let result = Bool(value) { - return result - } - return false + return getBoolPreference(key: "livePhoto", defaultValue: true) } set { - keychain["disablefilesapp"] = String(newValue) + setUserDefaults(newValue, forKey: "livePhoto") } } - @objc var livePhoto: Bool { + var disableCrashservice: Bool { get { - migrate(key: "livePhoto") - if let value = try? keychain.get("livePhoto"), let result = Bool(value) { - return result - } - return true + return getBoolPreference(key: "crashservice", defaultValue: false) } set { - keychain["livePhoto"] = String(newValue) + setUserDefaults(newValue, forKey: "crashservice") } } - @objc var disableCrashservice: Bool { + /// Stores and retrieves the current log level from the keychain. + var log: NKLogLevel { get { - migrate(key: "crashservice") - if let value = try? keychain.get("crashservice"), let result = Bool(value) { - return result - } - return false + let value = getIntPreference(key: "logLevel", defaultValue: NKLogLevel.normal.rawValue) + return NKLogLevel(rawValue: value) ?? NKLogLevel.normal } set { - keychain["crashservice"] = String(newValue) + setUserDefaults(newValue.rawValue, forKey: "logLevel") } } - @objc var logLevel: Int { + var accountRequest: Bool { get { - migrate(key: "logLevel") - if let value = try? keychain.get("logLevel"), let result = Int(value) { - return result - } - return 1 + return getBoolPreference(key: "accountRequest", defaultValue: false) } set { - keychain["logLevel"] = String(newValue) + setUserDefaults(newValue, forKey: "accountRequest") } } - @objc var accountRequest: Bool { + var removePhotoCameraRoll: Bool { get { - migrate(key: "accountRequest") - if let value = try? keychain.get("accountRequest"), let result = Bool(value) { - return result - } - return false + return getBoolPreference(key: "removePhotoCameraRoll", defaultValue: false) } set { - keychain["accountRequest"] = String(newValue) + setUserDefaults(newValue, forKey: "removePhotoCameraRoll") } } - @objc var removePhotoCameraRoll: Bool { + var privacyScreenEnabled: Bool { get { - migrate(key: "removePhotoCameraRoll") - if let value = try? keychain.get("removePhotoCameraRoll"), let result = Bool(value) { - return result + if NCBrandOptions.shared.enforce_privacyScreenEnabled { + return true } - return false + return getBoolPreference(key: "privacyScreen", defaultValue: false) } set { - keychain["removePhotoCameraRoll"] = String(newValue) + setUserDefaults(newValue, forKey: "privacyScreen") } } - @objc var privacyScreenEnabled: Bool { + var cleanUpDay: Int { get { - migrate(key: "privacyScreen") - if let value = try? keychain.get("privacyScreen"), let result = Bool(value) { - return result - } - return false - } - set { - keychain["privacyScreen"] = String(newValue) - } - } - - @objc 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") } } @@ -328,141 +257,134 @@ import KeychainAccess 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 { - keychain["fileNameMask"] = String(newValue) + 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 { + 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?) { @@ -471,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 - } + return getBoolPreference(key: "personalfilesonly", account: account, defaultValue: 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 - } + func setFavoriteOnTop(account: String, value: Bool) { + let userDefaultsKey = "favoriteOnTop" + "_\(account)" + setUserDefaults(value, forKey: userDefaultsKey) } - */ - func setTitleButtonHeader(account: String, value: String?) { - let key = "titleButtonHeader" + account - keychain[key] = value + func getFavoriteOnTop(account: String) -> Bool { + return getBoolPreference(key: "favoriteOnTop", account: account, defaultValue: true) } - 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) + 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? { @@ -613,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) } @@ -703,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] { @@ -776,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 ed90feeb17..7eddf30c48 100644 --- a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift +++ b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift @@ -159,7 +159,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