diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index e0a2d32314..ac7ee69b05 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -265,6 +265,7 @@ F71F6D0B2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; }; F71F6D0C2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; }; F71F6D0D2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; }; + F71FA7992F3508C600E86192 /* NCNetworking+WebDAV.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E2F2B73A86700A462C7 /* NCNetworking+WebDAV.swift */; }; F722133B2D40EF9D002F7438 /* NCFilesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F722133A2D40EF8C002F7438 /* NCFilesNavigationController.swift */; }; F7226EDC1EE4089300EBECB1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7226EDB1EE4089300EBECB1 /* Main.storyboard */; }; F722F0112CFF569500065FB5 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F722F0102CFF569500065FB5 /* MainInterface.storyboard */; }; @@ -308,7 +309,6 @@ F7327E202B73A42F00A462C7 /* NCNetworking+Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E1F2B73A42F00A462C7 /* NCNetworking+Download.swift */; }; F7327E232B73A42F00A462C7 /* NCNetworking+Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E1F2B73A42F00A462C7 /* NCNetworking+Download.swift */; }; F7327E302B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E2F2B73A86700A462C7 /* NCNetworking+WebDAV.swift */; }; - F7327E322B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E2F2B73A86700A462C7 /* NCNetworking+WebDAV.swift */; }; F7327E352B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E342B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift */; }; F7327E3B2B73B8D600A462C7 /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7AC1CAF28AB94490032D99F /* Array+Extension.swift */; }; F732D23327CF8AED000B0F1B /* NCPlayerToolBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = F732D23227CF8AED000B0F1B /* NCPlayerToolBar.xib */; }; @@ -599,6 +599,7 @@ F7864ACE2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; F7864ACF2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; F7864AD22A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; + F7865FF12F39D32F00D09AE4 /* NCCollectionViewCommon+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7865FF02F39D32500D09AE4 /* NCCollectionViewCommon+Search.swift */; }; F787704F22E7019900F287A9 /* NCShareLinkCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F787704E22E7019900F287A9 /* NCShareLinkCell.xib */; }; F787AC09298BCB4A0001BB00 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F787AC08298BCB4A0001BB00 /* SVGKitSwift */; }; F787AC0B298BCB540001BB00 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F787AC0A298BCB540001BB00 /* SVGKitSwift */; }; @@ -867,7 +868,6 @@ F7E742FB2EC0A5FD00E2362A /* NCManageDatabase+Metadata+Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B769A72B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift */; }; F7E742FC2EC0A5FD00E2362A /* NCManageDatabase+Metadata+Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B769A72B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift */; }; F7E7AEA52BA32C6500512E52 /* NCCollectionViewDownloadThumbnail.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E7AEA42BA32C6500512E52 /* NCCollectionViewDownloadThumbnail.swift */; }; - F7E7AEA72BA32D0000512E52 /* NCCollectionViewUnifiedSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E7AEA62BA32D0000512E52 /* NCCollectionViewUnifiedSearch.swift */; }; F7E8A391295DC5E0006CB2D0 /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E8A390295DC5E0006CB2D0 /* View+Extension.swift */; }; F7E98C1627E0D0FC001F9F19 /* NCManageDatabase+Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E98C1527E0D0FC001F9F19 /* NCManageDatabase+Video.swift */; }; F7E98C1727E0D0FC001F9F19 /* NCManageDatabase+Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E98C1527E0D0FC001F9F19 /* NCManageDatabase+Video.swift */; }; @@ -1526,6 +1526,7 @@ F785129A2D79899E0087DDD0 /* NCNetworking+TermsOfService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCNetworking+TermsOfService.swift"; sourceTree = ""; }; F785EE9C246196DF00B3F945 /* NCNetworkingE2EE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCNetworkingE2EE.swift; sourceTree = ""; }; F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+LocalFile.swift"; sourceTree = ""; }; + F7865FF02F39D32500D09AE4 /* NCCollectionViewCommon+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+Search.swift"; sourceTree = ""; }; F787704E22E7019900F287A9 /* NCShareLinkCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCShareLinkCell.xib; sourceTree = ""; }; F78A10BE29322E8A008499B8 /* NCManageDatabase+Directory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Directory.swift"; sourceTree = ""; }; F78A18B523CDD07D00F681F3 /* NCViewerRichWorkspaceWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCViewerRichWorkspaceWebView.swift; sourceTree = ""; }; @@ -1769,7 +1770,6 @@ F7E45E6D21E75BF200579249 /* ja-JP */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ja-JP"; path = "ja-JP.lproj/Localizable.strings"; sourceTree = ""; }; F7E4D9C322ED929B003675FD /* NCShareCommentsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareCommentsCell.swift; sourceTree = ""; }; F7E7AEA42BA32C6500512E52 /* NCCollectionViewDownloadThumbnail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCollectionViewDownloadThumbnail.swift; sourceTree = ""; }; - F7E7AEA62BA32D0000512E52 /* NCCollectionViewUnifiedSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCollectionViewUnifiedSearch.swift; sourceTree = ""; }; F7E8A390295DC5E0006CB2D0 /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = ""; }; F7E98C1527E0D0FC001F9F19 /* NCManageDatabase+Video.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Video.swift"; sourceTree = ""; }; F7EB9B122BBC12F300EDF036 /* UIApplication+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+Extension.swift"; sourceTree = ""; }; @@ -2493,6 +2493,7 @@ F7D890742BD25C570050B8A6 /* NCCollectionViewCommon+DragDrop.swift */, F799DF872C4B83CC003410B5 /* NCCollectionViewCommon+EasyTipView.swift */, F799DF8A2C4B84EB003410B5 /* NCCollectionViewCommon+EndToEndInitialize.swift */, + F7865FF02F39D32500D09AE4 /* NCCollectionViewCommon+Search.swift */, F7CCAB502ECF315F00F8E68B /* NCCollectionViewCommon+SyncMetadata.swift */, F778231D2C42C07C001BB94F /* NCCollectionViewCommon+MediaLayout.swift */, F36E64F62B9245210085ABB5 /* NCCollectionViewCommon+SelectTabBarDelegate.swift */, @@ -2500,7 +2501,6 @@ F38F71242B6BBDC300473CDC /* NCCollectionViewCommonSelectTabBar.swift */, F7C1EEA425053A9C00866ACC /* NCCollectionViewDataSource.swift */, F7E7AEA42BA32C6500512E52 /* NCCollectionViewDownloadThumbnail.swift */, - F7E7AEA62BA32D0000512E52 /* NCCollectionViewUnifiedSearch.swift */, ); path = "Collection Common"; sourceTree = ""; @@ -4292,10 +4292,10 @@ AF22B208277B4E4C00DAB0CC /* NCCreateFormUploadConflictCell.swift in Sources */, F73EF7C22B02250B0087E6E9 /* NCManageDatabase+GPS.swift in Sources */, F7148041262EBE4000693E51 /* NCShareExtension.swift in Sources */, + F71FA7992F3508C600E86192 /* NCNetworking+WebDAV.swift in Sources */, F76B3CCF1EAE01BD00921AC9 /* NCBrand.swift in Sources */, F72944F32A84246400246839 /* NCEndToEndMetadataV20.swift in Sources */, F7BAADCC1ED5A87C00B7EAD4 /* NCManageDatabase.swift in Sources */, - F7327E322B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4418,7 +4418,6 @@ CB3666201AF7550816B5CD6A /* NCContextMenuComment.swift in Sources */, 2F96A1BAFB10ACFEAC68EF1C /* NCContextMenuPlayerTracks.swift in Sources */, F7E402332BA89551007E5609 /* NCTrash+Networking.swift in Sources */, - F7E7AEA72BA32D0000512E52 /* NCCollectionViewUnifiedSearch.swift in Sources */, F73EF7A72B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */, F33918C42C7CD8F2002D9AA1 /* FileNameValidator+Extensions.swift in Sources */, F39170AD2CB82024006127BC /* FileAutoRenamer+Extensions.swift in Sources */, @@ -4705,6 +4704,7 @@ F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */, F704B5E52430AA8000632F5F /* NCCreateFormUploadConflict.swift in Sources */, F765608F23BF813600765969 /* NCContentPresenter.swift in Sources */, + F7865FF12F39D32F00D09AE4 /* NCCollectionViewCommon+Search.swift in Sources */, F7327E352B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift in Sources */, F76687072B7D067400779E3F /* NCAudioRecorderViewController.swift in Sources */, AA8E03DA2D2ED83300E7E89C /* TransientShare.swift in Sources */, @@ -5759,7 +5759,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -5786,7 +5786,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 7.2.3; + MARKETING_VERSION = 7.2.4; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-v"; OTHER_LDFLAGS = ""; @@ -5825,7 +5825,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -5849,7 +5849,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 7.2.3; + MARKETING_VERSION = 7.2.4; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-v"; OTHER_LDFLAGS = ""; @@ -6122,7 +6122,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/nextcloud/NextcloudKit"; requirement = { - branch = 7.2.4; + branch = main; kind = branch; }; }; diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index b4b8615114..8436ac37d3 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -131,6 +131,7 @@ class tableMetadata: Object { /// Used only for UI state (not persisted, not observed by Realm) var isOffline: Bool = false + var section: String = "" override static func primaryKey() -> String { return "ocId" diff --git a/iOSClient/Extensions/UINavigationController+Extension.swift b/iOSClient/Extensions/UINavigationController+Extension.swift index 05ab49b763..46b9a063fb 100644 --- a/iOSClient/Extensions/UINavigationController+Extension.swift +++ b/iOSClient/Extensions/UINavigationController+Extension.swift @@ -1,25 +1,6 @@ -// -// UINavigationController+Extension.swift -// Nextcloud -// -// Created by Marino Faggiana on 02/08/2022. -// Copyright © 2022 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: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import Foundation import UIKit diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index 0abb5a8221..1285c12e85 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -185,27 +185,11 @@ class NCFiles: NCCollectionViewCommon { startSyncMetadata(metadatas: self.dataSource.getMetadatas()) } - Task { - await networking.networkingTasks.cancel(identifier: "\(self.serverUrl)_NCFiles") - } + await networking.networkingTasks.cancel(identifier: "\(self.serverUrl)_NCFiles") guard !isSearchingMode else { - return networkSearch() - } - - func downloadMetadata(_ metadata: tableMetadata) async -> Bool { - let fileSize = utilityFileSystem.fileProviderStorageSize(metadata.ocId, - fileName: metadata.fileNameView, - userId: metadata.userId, - urlBase: metadata.urlBase) - guard fileSize > 0 else { return false } - - if let tblLocalFile = await database.getTableLocalFileAsync(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) { - if tblLocalFile.etag != metadata.etag { - return true - } - } - return false + await self.search() + return } let resultsReadFolder = await networkReadFolderAsync(serverUrl: self.serverUrl, forced: forced) @@ -216,7 +200,7 @@ class NCFiles: NCCollectionViewCommon { let metadatasForDownload: [tableMetadata] = resultsReadFolder.metadatas ?? self.dataSource.getMetadatas() Task.detached(priority: .utility) { for metadata in metadatasForDownload where !metadata.directory { - if await downloadMetadata(metadata) { + if await self.downloadMetadata(metadata) { if let metadata = await self.database.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, session: NCNetworking.shared.sessionDownload, selector: NCGlobal.shared.selectorDownloadFile, @@ -230,6 +214,23 @@ class NCFiles: NCCollectionViewCommon { await self.reloadDataSource() } + private func downloadMetadata(_ metadata: tableMetadata) async -> Bool { + let fileSize = utilityFileSystem.fileProviderStorageSize(metadata.ocId, + fileName: metadata.fileNameView, + userId: metadata.userId, + urlBase: metadata.urlBase) + guard fileSize > 0 else { + return false + } + + if let tblLocalFile = await database.getTableLocalFileAsync(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) { + if tblLocalFile.etag != metadata.etag { + return true + } + } + return false + } + private func networkReadFolderAsync(serverUrl: String, forced: Bool) async -> (metadatas: [tableMetadata]?, error: NKError, reloadRequired: Bool) { var reloadRequired: Bool = false let resultsReadFile = await NCNetworking.shared.readFileAsync(serverUrlFileName: serverUrl, account: session.account) { task in diff --git a/iOSClient/GUI/Lucid Banner/BannerView.swift b/iOSClient/GUI/Lucid Banner/BannerView.swift index a029816589..005a702a8b 100644 --- a/iOSClient/GUI/Lucid Banner/BannerView.swift +++ b/iOSClient/GUI/Lucid Banner/BannerView.swift @@ -75,14 +75,16 @@ func showInfoBannerActiveScenes(title: String = "_error_", text: String, footnote: String? = nil, foregroundColor: UIColor = .label, - backgroundColor: UIColor = .systemBackground) async { + backgroundColor: UIColor = .systemBackground, + errorCode: Int? = nil) async { for scene in UIApplication.shared.foregroundActiveScenes { await showInfoBanner(scene: scene, title: title, text: text, footnote: footnote, foregroundColor: foregroundColor, - backgroundColor: backgroundColor) + backgroundColor: backgroundColor, + errorCode: errorCode) } } @@ -92,13 +94,15 @@ func showInfoBanner(controller: UITabBarController?, text: String, footnote: String? = nil, foregroundColor: UIColor = .label, - backgroundColor: UIColor = .systemBackground) async { + backgroundColor: UIColor = .systemBackground, + errorCode: Int? = nil) async { let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene await showInfoBanner(scene: scene, title: title, text: text, foregroundColor: foregroundColor, - backgroundColor: backgroundColor) + backgroundColor: backgroundColor, + errorCode: errorCode) } @MainActor @@ -107,13 +111,15 @@ func showInfoBanner(sceneIdentifier: String?, text: String, footnote: String? = nil, foregroundColor: UIColor = .label, - backgroundColor: UIColor = .systemBackground) async { + backgroundColor: UIColor = .systemBackground, + errorCode: Int? = nil) async { await showInfoBanner(controller: SceneManager.shared.getController(sceneIdentifier: sceneIdentifier), title: title, text: text, footnote: footnote, foregroundColor: foregroundColor, - backgroundColor: backgroundColor) + backgroundColor: backgroundColor, + errorCode: errorCode) } #endif @@ -124,8 +130,12 @@ func showInfoBanner(scene: UIWindowScene?, text: String, footnote: String? = nil, foregroundColor: UIColor = .label, - backgroundColor: UIColor = .systemBackground) async { + backgroundColor: UIColor = .systemBackground, + errorCode: Int? = nil) async { #if !EXTENSION + guard !bannerContainsErrorCode(errorCode: errorCode) else { + return + } let scene = scene ?? UIApplication.shared.mainAppWindow?.windowScene #endif guard let window = scene?.windows.first else { @@ -229,19 +239,8 @@ func showErrorBanner(scene: UIWindowScene?, sleepBefore: Double = 1, errorCode: Int) async { #if !EXTENSION - // Prevent repeated display of the same user-facing error during the current foreground session. - // If this error code has already been shown, do nothing. - // Otherwise, record it and allow the UX notification to be displayed once. - if shownErrors.contains(errorCode) { + guard !bannerContainsErrorCode(errorCode: errorCode) else { return - } else { - // Coalesce user-facing errors across the current foreground session. - // The same error code is shown to the user only once. - // Error 507 (insufficient storage) is recorded to avoid repeated UX notifications - // for subsequent uploads failing for the same reason. - if errorCode == 507 { - shownErrors.insert(errorCode) - } } let scene = scene ?? UIApplication.shared.mainAppWindow?.windowScene #endif @@ -279,6 +278,34 @@ func showErrorBanner(scene: UIWindowScene?, } } +// MARK: - Helper +#if !EXTENSION +func bannerContainsErrorCode(errorCode: Int?) -> Bool { + guard let errorCode else { + return false + } + // List of errors not to be displayed + if errorCode == -999 { + return true + } + // Prevent repeated display of the same user-facing error during the current foreground session. + // If this error code has already been shown, do nothing. + // Otherwise, record it and allow the UX notification to be displayed once. + if shownErrors.contains(errorCode) { + return true + } else { + // Coalesce user-facing errors across the current foreground session. + // The same error code is shown to the user only once. + // Error 401 (maintenance mode) + // Error 507 (insufficient storage) + if errorCode == 401 || errorCode == 507 { + shownErrors.insert(errorCode) + } + return false + } +} +#endif + // MARK: - SwiftUI struct MessageBannerView: View { diff --git a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift index 99ca24292c..e788a6b1e2 100644 --- a/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift +++ b/iOSClient/Main/Collection Common/Cell/NCCellProtocol.swift @@ -17,6 +17,7 @@ protocol NCCellProtocol { var favoriteImageView: UIImageView? { get set } var shareImageView: UIImageView? { get set } var separatorView: UIView? { get set } + var tagSeparator: UILabel? { get set } func titleInfoTrailingFull() func writeInfoDateSize(date: NSDate, size: Int64) @@ -81,6 +82,11 @@ extension NCCellProtocol { set {} } + var tagSeparator: UILabel? { + get { return nil } + set {} + } + func titleInfoTrailingFull() {} func writeInfoDateSize(date: NSDate, size: Int64) {} func setButtonMore(image: UIImage) {} diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 56c3b3bf33..2f4df564b6 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -79,6 +79,10 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto get { return separator } set { separator = newValue } } + var tagSeparator: UILabel? { + get { return labelInfoSeparator } + set { labelInfoSeparator = newValue } + } override var accessibilityIdentifier: String? { get { diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift index be4595a357..7bab5832e7 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDataSource.swift @@ -189,7 +189,6 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.info?.text = metadata.sessionError } else { cell.subInfo?.isHidden = false - cell.writeInfoDateSize(date: metadata.date, size: metadata.size) } @@ -425,8 +424,10 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { cell.title?.textColor = NCBrandColor.shared.textColor cell.title?.font = .systemFont(ofSize: 15) - if isSearchingMode, let literalSearch = self.literalSearch, let title = cell.title?.text { - let longestWordRange = (title.lowercased() as NSString).range(of: literalSearch) + if isSearchingMode, + let searchResultStore, + let title = cell.title?.text { + let longestWordRange = (title.lowercased() as NSString).range(of: searchResultStore) let attributedString = NSMutableAttributedString(string: title, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15)]) attributedString.setAttributes([NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 15), NSAttributedString.Key.foregroundColor: UIColor.systemBlue], range: longestWordRange) cell.title?.attributedText = attributedString @@ -435,6 +436,11 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { // TAGS cell.setTags(tags: Array(metadata.tags)) + // SearchingMode - TAG Separator Hidden + if isSearchingMode { + cell.tagSeparator?.isHidden = true + } + // Layout photo if isLayoutPhoto { let width = UIScreen.main.bounds.width / CGFloat(self.numberOfColumns) @@ -509,13 +515,13 @@ extension NCCollectionViewCommon: UICollectionViewDataSource { if isSearchingMode { emptyImage = utility.loadImage(named: "magnifyingglass", colors: [NCBrandColor.shared.getElement(account: session.account)]) - if self.searchDataSourceTask?.state == .running { + if self.searchTask?.state == .running { emptyTitle = NSLocalizedString("_search_in_progress_", comment: "") } else { emptyTitle = NSLocalizedString("_search_no_record_found_", comment: "") } emptyDescription = NSLocalizedString("_search_instruction_", comment: "") - } else if self.searchDataSourceTask?.state == .running || !self.dataSource.getGetServerData() { + } else if self.searchTask?.state == .running || !self.dataSource.getGetServerData() { emptyImage = utility.loadImage(named: "wifi", colors: [NCBrandColor.shared.getElement(account: session.account)]) emptyTitle = NSLocalizedString("_request_in_progress_", comment: "") emptyDescription = "" diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift new file mode 100644 index 0000000000..1daf8ea3dc --- /dev/null +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift @@ -0,0 +1,330 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2026 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation +import NextcloudKit + +extension NCCollectionViewCommon { + func search() async { + guard !networkSearchInProgress, + !session.account.isEmpty, + let text = searchResultText, + !text.isEmpty else { + return + } + let capabilities = await NKCapabilities.shared.getCapabilities(for: session.account) + + self.networkSearchInProgress = true + // STOP PREEMPTIVE SYNC METADATA + self.stopSyncMetadata() + // Clear datasotce + self.dataSource.removeAll() + self.collectionView.reloadData() + + if capabilities.serverVersionMajor >= global.nextcloudVersion20 { + await unifiedSearch(text: text) + } else { + await searchLiteral(text: text) + } + } + + // MARK: - search Literal + + private func searchLiteral(text: String) async { + defer { + self.networkSearchInProgress = false + } + + let showHiddenFiles = NCPreferences().getShowHiddenFiles(account: session.account) + let urlBase = NCSession.shared.getSession(account: session.account).urlBase + + let results = await NextcloudKit.shared.searchLiteralAsync( + serverUrl: urlBase, + depth: "infinity", + literal: text, + showHiddenFiles: showHiddenFiles, + account: self.session.account + ) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier( + account: self.session.account, + path: urlBase, + name: "searchLiteral" + ) + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + self.searchTask = task + } + } + + if results.error == .success, + let files = results.files { + let (_, metadatas) = await NCManageDatabaseCreateMetadata().convertFilesToMetadatasAsync(files) + NCManageDatabase.shared.addMetadatas(metadatas) + self.dataSource = NCCollectionViewDataSource( + metadatas: metadatas, + layoutForView: self.layoutForView, + account: self.session.account + ) + } else { + await showErrorBanner(controller: self.controller, + text: results.error.errorDescription, + errorCode: results.error.errorCode) + } + + self.collectionView.reloadData() + } + + // MARK: - Unifield Search + + private func unifiedSearch(text: String) async { + defer { + networkSearchInProgress = false + Task { + if !isSearchingMode { + self.dataSource.removeAll() + await self.reloadDataSource() + } + } + } + + // Store the search + self.searchResultStore = text + + // ---> In This folder + let metadatas = await NCManageDatabase.shared.getMetadatasAsync(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView CONTAINS[c] %@", session.account, self.serverUrl, text)) ?? [] + for metadatas in metadatas { + metadatas.section = NSLocalizedString("_in_this_folder_", comment: "") + } + + self.dataSource = NCCollectionViewDataSource( + metadatas: metadatas, + layoutForView: layoutForView, + isSections: true, + searchResults: [], + account: session.account + ) + self.collectionView.reloadData() + + // ---> Get providers + let results = await NextcloudKit.shared.unifiedSearchProviders(account: session.account) { _ in + // example filter + // ["calendar", "files", "fulltextsearch"].contains(provider.id) + return true + } taskHandler: { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier( + account: self.session.account, + name: "unifiedSearchProviders" + ) + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + self.searchTask = task + self.collectionView.reloadData() + } + } + + if results.error != .success { + await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) + } + + guard isSearchingMode, + results.error == .success, + let providers = results.providers else { + return + } + + // Added providers in DataSource + self.dataSource.setProviders(providers) + // "files" first position + /* + if let index = providers.firstIndex(where: { $0.id == NCGlobal.shared.appName }) { + let files = providers.remove(at: index) + providers.insert(files, at: 0) + } + */ + + // ---> Get metadatas for providers + for provider in providers { + let results = await NextcloudKit.shared.unifiedSearch( + providerId: provider.id, + term: text, + limit: 5, + cursor: 0, + timeout: 90, + account: session.account + ) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier( + account: self.session.account, + name: "unifiedSearch" + ) + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + self.searchTask = task + self.collectionView.reloadData() + } + } + + if results.error != .success { + await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) + } + + guard isSearchingMode, + self.searchResultText == text, + results.error == .success, + let searchResult = results.searchResult else { + return + } + + if let metadatas = await getSearchResultMetadatas( + session: session, + provider: provider, + searchResult: searchResult + ) { + self.dataSource.addSection(metadatas: metadatas, searchResult: searchResult) + self.collectionView.reloadData() + } + } + } + + func unifiedSearchMore(metadataForSection: NCMetadataForSection?) async { + defer { + metadataForSection?.unifiedSearchInProgress = false + Task { + if !isSearchingMode { + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferReloadDataSource(serverUrl: self.serverUrl, requestData: true, status: nil) + } + } + } + } + + guard let metadataForSection = metadataForSection, + let lastSearchResult = metadataForSection.lastSearchResult, + let cursor = lastSearchResult.cursor, + let searchResultStore else { + return + } + + metadataForSection.unifiedSearchInProgress = true + self.collectionView.reloadData() + + let results = await NextcloudKit.shared.unifiedSearch( + providerId: lastSearchResult.id, + term: searchResultStore, + limit: 5, + cursor: cursor, + timeout: 60, + account: session.account + ) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier( + account: self.session.account, + name: "unifiedSearch" + ) + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + self.searchTask = task + self.collectionView.reloadData() + } + } + + if results.error != .success { + await showErrorBanner(controller: self.controller, text: results.error.errorDescription, errorCode: results.error.errorCode) + } + + guard isSearchingMode, + results.error == .success, + let searchResult = results.searchResult, + let provider = self.dataSource.getProvider(id: searchResult.id) else { + return + } + + if let metadatas = await getSearchResultMetadatas( + session: session, + provider: provider, + searchResult: searchResult + ) { + self.dataSource.appendMetadatasToSection(metadatas, metadataForSection: metadataForSection, lastSearchResult: searchResult) + self.collectionView.reloadData() + } + } + + // MARK: - Helper + + private func getSearchResultMetadatas(session: NCSession.Session, + provider: NKSearchProvider, + searchResult: NKSearchResult, + ) async -> [tableMetadata]? { + var metadatas: [tableMetadata] = [] + + switch provider.id { + case "files": + for entry in searchResult.entries { + if let filePath = entry.filePath { + if let metadata = await loadMetadata(session: session, + provider: provider, + filePath: filePath) { + metadatas.append(metadata) + } + } else { + print(#function, "[ERROR]: File search entry has no path: \(entry)") + } + } + + return(metadatas) + + case "fulltextsearch": + for entry in searchResult.entries { + let url = URLComponents(string: entry.resourceURL) + guard let dir = url?.queryItems?["dir"]?.value, let filename = url?.queryItems?["scrollto"]?.value else { + return(nil) + } + if let metadata = await NCManageDatabase.shared.getMetadataAsync( + predicate: NSPredicate(format: "account == %@ && path == %@ && fileName == %@", session.account, "/remote.php/dav/files/" + session.user + dir, filename)) { + metadatas.append(metadata) + } else { + if let metadata = await loadMetadata(session: session, + provider: provider, + filePath: dir + filename) { + metadatas.append(metadata) + } + } + + } + return(metadatas) + default: + for entry in searchResult.entries { + let metadata = await NCManageDatabaseCreateMetadata().createMetadataAsync( + fileName: entry.title, + ocId: NSUUID().uuidString, + serverUrl: session.urlBase, + url: entry.resourceURL, + isUrl: true, + name: searchResult.id, + subline: entry.subline, + iconUrl: entry.thumbnailURL, + session: session, + sceneIdentifier: nil + ) + metadata.section = provider.name + metadatas.append(metadata) + } + return(metadatas) + } + } + + private func loadMetadata(session: NCSession.Session, + provider: NKSearchProvider, + filePath: String) async -> tableMetadata? { + let urlPath = session.urlBase + "/remote.php/dav/files/" + session.user + filePath + let results = await NCNetworking.shared.readFileAsync(serverUrlFileName: urlPath, + account: session.account + ) + guard let metadata = results.metadata else { + return nil + } + metadata.section = provider.name + + NCManageDatabase.shared.addMetadata(metadata) + return metadata + } +} diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 898dcdc97c..d6f3f910a6 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -36,22 +36,22 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, var richWorkspaceText: String? var sectionFirstHeader: NCSectionFirstHeader? var sectionFirstHeaderEmptyData: NCSectionFirstHeaderEmptyData? - var isSearchingMode: Bool = false var networkSearchInProgress: Bool = false var layoutForView: NCDBLayoutForView? - var searchDataSourceTask: URLSessionTask? - var providers: [NKSearchProvider]? - var searchResults: [NKSearchResult]? var listLayout = NCListLayout() var gridLayout = NCGridLayout() var mediaLayout = NCMediaLayout() var layoutType = NCGlobal.shared.layoutList - var literalSearch: String? var tabBarSelect: NCCollectionViewCommonSelectTabBar? var attributesZoomIn: UIMenuElement.Attributes = [] var attributesZoomOut: UIMenuElement.Attributes = [] var tipViewAccounts: EasyTipView? var syncMetadatasTask: Task? + // Search + var isSearchingMode: Bool = false + var searchTask: URLSessionTask? + var searchResultText: String? + var searchResultStore: String? // DECLARE var layoutKey = "" @@ -168,16 +168,27 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, view.backgroundColor = .systemBackground collectionView.backgroundColor = .systemBackground refreshControl.tintColor = .clear + definesPresentationContext = true if enableSearchBar { searchController = UISearchController(searchResultsController: nil) searchController?.searchResultsUpdater = self searchController?.obscuresBackgroundDuringPresentation = false searchController?.delegate = self - searchController?.searchBar.delegate = self - searchController?.searchBar.autocapitalizationType = .none + + let searchBar = searchController?.searchBar + searchBar?.delegate = self + searchBar?.autocapitalizationType = .none + searchBar?.backgroundImage = UIImage() + + let textField = searchController?.searchBar.searchTextField + textField?.backgroundColor = .systemGray.withAlphaComponent(0.30) + textField?.borderStyle = .none + textField?.layer.cornerRadius = 20 + textField?.clipsToBounds = true + navigationItem.searchController = searchController - navigationItem.hidesSearchBarWhenScrolling = true + navigationItem.hidesSearchBarWhenScrolling = false } // Cell @@ -233,7 +244,7 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, registerForTraitChanges([UITraitUserInterfaceStyle.self]) { [weak self] (view: NCCollectionViewCommon, _) in guard let self else { return } - self.sectionFirstHeader?.setRichWorkspaceColor(style: view.traitCollection.userInterfaceStyle) + sectionFirstHeader?.setRichWorkspaceColor(style: view.traitCollection.userInterfaceStyle) } NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterChangeTheming), object: nil, queue: .main) { _ in @@ -305,13 +316,12 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - self.networking.cancelUnifiedSearchFiles() + self.searchTask?.cancel() dismissTip() // Cancel Queue & Retrieves Properties self.networking.downloadThumbnailQueue.cancelAll() - self.networking.unifiedSearchQueue.cancelAll() - searchDataSourceTask?.cancel() + searchTask?.cancel() } override func viewDidDisappear(_ animated: Bool) { @@ -464,52 +474,58 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, // MARK: - SEARCH func searchController(enabled: Bool) { - guard enableSearchBar else { return } + guard enableSearchBar else { + return + } searchController?.searchBar.isUserInteractionEnabled = enabled + if enabled { searchController?.searchBar.alpha = 1 } else { searchController?.searchBar.alpha = 0.3 - } } func updateSearchResults(for searchController: UISearchController) { - self.literalSearch = searchController.searchBar.text + searchResultText = searchController.searchBar.text } func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { - isSearchingMode = true - self.providers?.removeAll() - self.dataSource.removeAll() - Task { - await self.reloadDataSource() - } // TIP dismissTip() // mainNavigationController?.hiddenPlusButton(true) + // + if !isSearchingMode { + self.isSearchingMode = true + self.dataSource.removeAll() + self.collectionView.reloadData() + } } func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { - if isSearchingMode && self.literalSearch?.count ?? 0 >= 2 { - networkSearch() + if isSearchingMode, + searchResultText?.count ?? 0 >= 2 { + Task { + await self.search() + } } } func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { - self.networking.cancelUnifiedSearchFiles() + // + mainNavigationController?.hiddenPlusButton(false) + self.searchTask?.cancel() self.isSearchingMode = false self.networkSearchInProgress = false - self.literalSearch = "" - self.providers?.removeAll() - self.dataSource.removeAll() + self.searchResultText = nil + self.searchResultStore = nil + Task { + self.dataSource.removeAll() await self.reloadDataSource() } - // - mainNavigationController?.hiddenPlusButton(false) } // MARK: - TAP EVENT @@ -657,113 +673,6 @@ class NCCollectionViewCommon: UIViewController, NCAccountSettingsModelDelegate, func getServerData(forced: Bool = false) async { } - @objc func networkSearch() { - guard !networkSearchInProgress else { - return - } - guard !session.account.isEmpty, - let literalSearch = literalSearch, - !literalSearch.isEmpty else { - return - } - let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() - - self.networkSearchInProgress = true - self.dataSource.removeAll() - Task { - await self.reloadDataSource() - } - - if capabilities.serverVersionMajor >= global.nextcloudVersion20 { - self.networking.unifiedSearchFiles(literal: literalSearch, account: session.account) { task in - self.searchDataSourceTask = task - Task { - await self.reloadDataSource() - } - } providers: { account, searchProviders in - self.providers = searchProviders - self.searchResults = [] - self.dataSource = NCCollectionViewDataSource(metadatas: [], - layoutForView: self.layoutForView, - providers: self.providers, - searchResults: self.searchResults, - account: account) - } update: { _, _, searchResult, metadatas in - guard let metadatas, !metadatas.isEmpty, self.isSearchingMode, let searchResult else { return } - self.networking.unifiedSearchQueue.addOperation(NCCollectionViewUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult)) - } completion: { _, _ in - Task { - await self.reloadDataSource() - } - self.networkSearchInProgress = false - } - } else { - self.networking.searchFiles(literal: literalSearch, account: session.account) { task in - self.searchDataSourceTask = task - Task { - await self.reloadDataSource() - } - } completion: { metadatasSearch, error in - Task { - guard let metadatasSearch, - error == .success, - self.isSearchingMode - else { - self.networkSearchInProgress = false - await self.reloadDataSource() - return - } - let ocId = metadatasSearch.map { $0.ocId } - let metadatas = await self.database.getMetadatasAsync(predicate: NSPredicate(format: "ocId IN %@", ocId), - withLayout: self.layoutForView, - withAccount: self.session.account) - - self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, - layoutForView: self.layoutForView, - providers: self.providers, - searchResults: self.searchResults, - account: self.session.account) - self.networkSearchInProgress = false - await self.reloadDataSource() - } - } - } - } - - func unifiedSearchMore(metadataForSection: NCMetadataForSection?) { - guard let metadataForSection = metadataForSection, let lastSearchResult = metadataForSection.lastSearchResult, let cursor = lastSearchResult.cursor, let term = literalSearch else { return } - - metadataForSection.unifiedSearchInProgress = true - Task { - await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: nil) - } - } - - self.networking.unifiedSearchFilesProvider(id: lastSearchResult.id, term: term, limit: 5, cursor: cursor, account: session.account) { task in - self.searchDataSourceTask = task - Task { - await self.reloadDataSource() - } - } completion: { _, searchResult, metadatas, error in - if error != .success { - Task { - await showErrorBanner(controller: self.controller, text: error.errorDescription, errorCode: error.errorCode) - } - } - - metadataForSection.unifiedSearchInProgress = false - guard let searchResult = searchResult, let metadatas = metadatas else { return } - self.dataSource.appendMetadatasToSection(metadatas, metadataForSection: metadataForSection, lastSearchResult: searchResult) - - Task { - await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: nil) - } - } - } - } - // MARK: - Push metadata func pushMetadata(_ metadata: tableMetadata) { @@ -893,7 +802,9 @@ extension NCCollectionViewCommon: NCSectionFirstHeaderDelegate { extension NCCollectionViewCommon: NCSectionFooterDelegate { func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { - unifiedSearchMore(metadataForSection: metadataForSection) + Task { + await unifiedSearchMore(metadataForSection: metadataForSection) + } } } @@ -936,7 +847,7 @@ extension NCCollectionViewCommon: NCTransferDelegate { if self.isSearchingMode { await self.debouncerNetworkSearch.call { - self.networkSearch() + await self.search() } } else if self.serverUrl == serverUrl || destination == self.serverUrl || self.serverUrl.isEmpty { await self.debouncerReloadDataSource.call { @@ -950,7 +861,7 @@ extension NCCollectionViewCommon: NCTransferDelegate { Task { if self.isSearchingMode { await self.debouncerNetworkSearch.call { - self.networkSearch() + await self.search() } return } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewDataSource.swift b/iOSClient/Main/Collection Common/NCCollectionViewDataSource.swift index 0de361ad3a..e937164631 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewDataSource.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewDataSource.swift @@ -12,9 +12,10 @@ class NCCollectionViewDataSource: NSObject { private let global = NCGlobal.shared private let database = NCManageDatabase.shared - private var sectionsValue: [String] = [] - private var providers: [NKSearchProvider]? + private var sections: [String] = [] + private var isSections: Bool = false private var searchResults: [NKSearchResult]? + private var providers: [NKSearchProvider]? private var metadatas: [tableMetadata] = [] private var metadatasForSection: [NCMetadataForSection] = [] private var layoutForView: NCDBLayoutForView? @@ -26,7 +27,7 @@ class NCCollectionViewDataSource: NSObject { init(metadatas: [tableMetadata], layoutForView: NCDBLayoutForView? = nil, - providers: [NKSearchProvider]? = nil, + isSections: Bool = false, searchResults: [NKSearchResult]? = nil, account: String? = nil) { super.init() @@ -38,11 +39,12 @@ class NCCollectionViewDataSource: NSObject { self.directoryOnTop = NCPreferences().getDirectoryOnTop(account: account) self.favoriteOnTop = NCPreferences().getFavoriteOnTop(account: account) } + // is Sections + self.isSections = isSections // unified search - self.providers = providers self.searchResults = searchResults - if let providers, !providers.isEmpty || (layoutForView?.groupBy != "none") { + if isSections || (layoutForView?.groupBy != "none") { createSections() } } @@ -57,14 +59,20 @@ class NCCollectionViewDataSource: NSObject { hasGetServerData = state } - // MARK: - + func setProviders(_ providers: [NKSearchProvider]) { + self.providers = providers + } + + func getProvider(id: String) -> NKSearchProvider? { + return providers?.filter({ $0.id == id}).first + } func removeAll() { + self.sections.removeAll() + self.searchResults?.removeAll() + self.providers?.removeAll() self.metadatas.removeAll() self.metadatasForSection.removeAll() - self.sectionsValue.removeAll() - self.providers = nil - self.searchResults = nil } func addSection(metadatas: [tableMetadata], searchResult: NKSearchResult?) { @@ -78,39 +86,25 @@ class NCCollectionViewDataSource: NSObject { } internal func createSections() { - for metadata in self.metadatas { - // skipped livePhoto VIDEO part - if metadata.isLivePhoto, metadata.classFile == NKTypeClassFile.video.rawValue { + for metadata in metadatas { + if metadata.isLivePhoto, + metadata.classFile == NKTypeClassFile.video.rawValue { continue } - let section = NSLocalizedString(self.getSectionValue(metadata: metadata), comment: "") - if !self.sectionsValue.contains(section) { - self.sectionsValue.append(section) + + if !sections.contains(metadata.section) { + sections.append(metadata.section) } } - // Unified search - if let providers = self.providers, !providers.isEmpty { - let sectionsDictionary = ThreadSafeDictionary() - for section in self.sectionsValue { - if let provider = providers.filter({ $0.id == section}).first { - sectionsDictionary[section] = provider.order - } - } - self.sectionsValue.removeAll() - let sectionsDictionarySorted = sectionsDictionary.sorted(by: {$0.value < $1.value }) - for section in sectionsDictionarySorted { - if section.key == global.appName { - self.sectionsValue.insert(section.key, at: 0) - } else { - self.sectionsValue.append(section.key) - } - } + // Section order + if isSections { + // like inserted } else { // normal let favorite = NSLocalizedString("favorite", comment: "").lowercased().firstUppercased let directory = NSLocalizedString("directory", comment: "").lowercased().firstUppercased - self.sectionsValue = self.sectionsValue.sorted { lhs, rhs in + self.sections = self.sections.sorted { lhs, rhs in // 1. favorite on top if favoriteOnTop { if lhs == favorite && rhs != favorite { @@ -137,21 +131,21 @@ class NCCollectionViewDataSource: NSObject { } } - for sectionValue in self.sectionsValue { - if !existsMetadataForSection(sectionValue: sectionValue) { - print("DATASOURCE: create metadata for section: " + sectionValue) - createMetadataForSection(sectionValue: sectionValue) + for section in self.sections { + if !existsMetadataForSection(section: section) { + print("DATASOURCE: create metadata for section: " + section) + createMetadataForSection(section: section) } } } - internal func createMetadataForSection(sectionValue: String) { + internal func createMetadataForSection(section: String) { var searchResult: NKSearchResult? - if let providers = self.providers, !providers.isEmpty, let searchResults = self.searchResults { - searchResult = searchResults.filter({ $0.id == sectionValue}).first + if isSections, let searchResults = self.searchResults { + searchResult = searchResults.filter({ $0.name == section}).first } - let metadatas = self.metadatas.filter({ getSectionValue(metadata: $0) == sectionValue}) - let metadataForSection = NCMetadataForSection(sectionValue: sectionValue, + let metadatas = self.metadatas.filter({ $0.section == section}) + let metadataForSection = NCMetadataForSection(section: section, metadatas: metadatas, lastSearchResult: searchResult, layoutForView: self.layoutForView, @@ -163,7 +157,7 @@ class NCCollectionViewDataSource: NSObject { // MARK: - func appendMetadatasToSection(_ metadatas: [tableMetadata], metadataForSection: NCMetadataForSection, lastSearchResult: NKSearchResult) { - guard let sectionIndex = getSectionIndex(metadataForSection.sectionValue) + guard let sectionIndex = getSectionIndex(metadataForSection.section) else { return } @@ -192,7 +186,7 @@ class NCCollectionViewDataSource: NSObject { } func getIndexPathMetadata(ocId: String) -> IndexPath? { - guard self.sectionsValue.isEmpty else { + guard self.sections.isEmpty else { return nil } @@ -204,15 +198,15 @@ class NCCollectionViewDataSource: NSObject { } func numberOfSections() -> Int { - guard !self.sectionsValue.isEmpty else { + guard !self.sections.isEmpty else { return 1 } - return self.sectionsValue.count + return self.sections.count } func numberOfItemsInSection(_ section: Int) -> Int { - if self.sectionsValue.isEmpty { + if self.sections.isEmpty { return metadatas.count } @@ -231,11 +225,12 @@ class NCCollectionViewDataSource: NSObject { return "" } - if let searchResults = self.searchResults, let searchResult = searchResults.filter({ $0.id == metadataForSection.sectionValue}).first { + if let searchResults = self.searchResults, + let searchResult = searchResults.filter({ $0.id == metadataForSection.section}).first { return searchResult.name } - return metadataForSection.sectionValue + return metadataForSection.section } func getFooterInformation() -> (directories: Int, files: Int, size: Int64) { @@ -279,36 +274,25 @@ class NCCollectionViewDataSource: NSObject { return numberOfSections == self.numberOfSections() } - internal func getSectionValue(metadata: tableMetadata) -> String { - switch self.layoutForView?.groupBy { - case "name", "none": - return NSLocalizedString(metadata.name, comment: "") - case "classFile": - return NSLocalizedString(metadata.classFile, comment: "").lowercased().firstUppercased - default: - return NSLocalizedString(metadata.name, comment: "") - } - } - - internal func getIndexMetadatasForSection(_ sectionValue: String) -> Int? { - return self.metadatasForSection.firstIndex(where: {$0.sectionValue == sectionValue }) + internal func getIndexMetadatasForSection(_ section: String) -> Int? { + return self.metadatasForSection.firstIndex(where: {$0.section == section }) } - internal func getSectionIndex(_ sectionValue: String) -> Int? { - return self.sectionsValue.firstIndex(where: {$0 == sectionValue }) + internal func getSectionIndex(_ section: String) -> Int? { + return self.sections.firstIndex(where: {$0 == section }) } - internal func existsMetadataForSection(sectionValue: String) -> Bool { - return !self.metadatasForSection.filter({ $0.sectionValue == sectionValue }).isEmpty + internal func existsMetadataForSection(section: String) -> Bool { + return !self.metadatasForSection.filter({ $0.section == section }).isEmpty } internal func getMetadataForSection(_ section: Int) -> NCMetadataForSection? { - guard section < sectionsValue.count, let metadataForSection = self.metadatasForSection.filter({ $0.sectionValue == sectionsValue[section]}).first else { return nil } + guard section < sections.count, let metadataForSection = self.metadatasForSection.filter({ $0.section == sections[section]}).first else { return nil } return metadataForSection } - internal func getMetadataForSection(_ sectionValue: String) -> NCMetadataForSection? { - guard let metadataForSection = self.metadatasForSection.filter({ $0.sectionValue == sectionValue }).first else { return nil } + internal func getMetadataForSection(_ section: String) -> NCMetadataForSection? { + guard let metadataForSection = self.metadatasForSection.filter({ $0.section == section }).first else { return nil } return metadataForSection } } @@ -316,7 +300,7 @@ class NCCollectionViewDataSource: NSObject { // MARK: - class NCMetadataForSection: NSObject { - var sectionValue: String + var section: String var metadatas: [tableMetadata] var lastSearchResult: NKSearchResult? var unifiedSearchInProgress: Bool = false @@ -334,8 +318,8 @@ class NCMetadataForSection: NSObject { public var numFile: Int = 0 public var totalSize: Int64 = 0 - init(sectionValue: String, metadatas: [tableMetadata], lastSearchResult: NKSearchResult?, layoutForView: NCDBLayoutForView?, favoriteOnTop: Bool, directoryOnTop: Bool) { - self.sectionValue = sectionValue + init(section: String, metadatas: [tableMetadata], lastSearchResult: NKSearchResult?, layoutForView: NCDBLayoutForView?, favoriteOnTop: Bool, directoryOnTop: Bool) { + self.section = section self.metadatas = metadatas self.lastSearchResult = lastSearchResult self.layoutForView = layoutForView diff --git a/iOSClient/Main/Collection Common/NCCollectionViewUnifiedSearch.swift b/iOSClient/Main/Collection Common/NCCollectionViewUnifiedSearch.swift deleted file mode 100644 index 3a926c9c9b..0000000000 --- a/iOSClient/Main/Collection Common/NCCollectionViewUnifiedSearch.swift +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later - -import Foundation -import UIKit -import Queuer -import NextcloudKit -import RealmSwift - -class NCCollectionViewUnifiedSearch: ConcurrentOperation, @unchecked Sendable { - var collectionViewCommon: NCCollectionViewCommon - var metadatas: [tableMetadata] - var searchResult: NKSearchResult - - init(collectionViewCommon: NCCollectionViewCommon, metadatas: [tableMetadata], searchResult: NKSearchResult) { - self.collectionViewCommon = collectionViewCommon - self.metadatas = metadatas - self.searchResult = searchResult - } - - override func start() { - guard !isCancelled else { return self.finish() } - - self.collectionViewCommon.dataSource.addSection(metadatas: metadatas, searchResult: searchResult) - self.collectionViewCommon.searchResults?.append(self.searchResult) - self.finish() - - Task { - await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: nil) - } - } - } -} diff --git a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFooter.xib b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFooter.xib index d0e5b012d8..95ea89d710 100644 --- a/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFooter.xib +++ b/iOSClient/Main/Collection Common/Section Header Footer/NCSectionFooter.xib @@ -1,8 +1,8 @@ - + - + @@ -13,12 +13,12 @@ -