From 1ef2af0f18baee319c4230392d609af1551711e5 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 29 Nov 2024 15:32:30 +0100 Subject: [PATCH] - SearchScopeDescriptor: new type to describe search scopes and instantiate them - SearchScope: - add new class property "descriptor" to allow each scope to return its own descriptor - subclasses: refactor to add "descriptor" implementations, encapsulating everything into its SearchScopeDescriptor that's needed to use each scope respectively, allowing to integrate a search scope with the rest of the app with a single line in SearchScopeDescriptor.all -SearchScope+Registry: extend SearchScope with: - a method to access available scopes based on context and cell style - a concept to allow users to pick a default scope and SearchedContent - ClientViewController: make use of the SearchScope.availableScopes() based on SearchScopeDescriptor.all --- ownCloud.xcodeproj/project.pbxproj | 8 ++++ .../Scopes/AccountSearchScope.swift | 41 ++++++++++++++++ .../Scopes/ServerSideSearchScope.swift | 13 +++++ .../Scopes/SingleFolderSearchScope.swift | 13 +++++ .../Search/Scopes/SearchScope+Registry.swift | 38 +++++++++++++++ .../Client/Search/Scopes/SearchScope.swift | 30 +----------- .../Search/Scopes/SearchScopeDescriptor.swift | 47 +++++++++++++++++++ .../ClientItemViewController.swift | 28 +---------- 8 files changed, 163 insertions(+), 55 deletions(-) create mode 100644 ownCloudAppShared/Client/Search/Scopes/SearchScope+Registry.swift create mode 100644 ownCloudAppShared/Client/Search/Scopes/SearchScopeDescriptor.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index b1003649c..44116a573 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -405,6 +405,8 @@ DC89EA6B29959BD200BFF393 /* AppStateActionRestoreNavigationBookmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC89EA6A29959BD200BFF393 /* AppStateActionRestoreNavigationBookmark.swift */; }; DC8AA7BE29DC154400BFF393 /* ItemLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC8AA7BD29DC154400BFF393 /* ItemLayout.swift */; }; DC8BFC0E2A14CAB900BFF393 /* PocketSVG in Frameworks */ = {isa = PBXBuildFile; productRef = DC8BFC0D2A14CAB900BFF393 /* PocketSVG */; }; + DC8C07262CF9D6CC007B70E6 /* SearchScopeDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC8C07252CF9D6CC007B70E6 /* SearchScopeDescriptor.swift */; }; + DC8C072B2CF9DCC9007B70E6 /* SearchScope+Registry.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC8C072A2CF9DCC9007B70E6 /* SearchScope+Registry.swift */; }; DC8E99DC297E79E900594697 /* BrowserNavigationHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC8E99DB297E79E900594697 /* BrowserNavigationHistory.swift */; }; DC8E99E2297E906200594697 /* OCLicenseQAProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DC8E99DE297E8D3800594697 /* OCLicenseQAProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC8E99E3297E906700594697 /* OCLicenseQAProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC8E99DF297E8D3800594697 /* OCLicenseQAProvider.m */; }; @@ -1417,6 +1419,8 @@ DC89EA6829958DF500BFF393 /* UIViewController+BrowserNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+BrowserNavigation.swift"; sourceTree = ""; }; DC89EA6A29959BD200BFF393 /* AppStateActionRestoreNavigationBookmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateActionRestoreNavigationBookmark.swift; sourceTree = ""; }; DC8AA7BD29DC154400BFF393 /* ItemLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemLayout.swift; sourceTree = ""; }; + DC8C07252CF9D6CC007B70E6 /* SearchScopeDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchScopeDescriptor.swift; sourceTree = ""; }; + DC8C072A2CF9DCC9007B70E6 /* SearchScope+Registry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchScope+Registry.swift"; sourceTree = ""; }; DC8E99DB297E79E900594697 /* BrowserNavigationHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserNavigationHistory.swift; sourceTree = ""; }; DC8E99DE297E8D3800594697 /* OCLicenseQAProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseQAProvider.h; sourceTree = ""; }; DC8E99DF297E8D3800594697 /* OCLicenseQAProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCLicenseQAProvider.m; sourceTree = ""; }; @@ -3064,6 +3068,8 @@ isa = PBXGroup; children = ( DCB5D5A628632C17004AF425 /* SearchScope.swift */, + DC8C07252CF9D6CC007B70E6 /* SearchScopeDescriptor.swift */, + DC8C072A2CF9DCC9007B70E6 /* SearchScope+Registry.swift */, ); path = Scopes; sourceTree = ""; @@ -4778,6 +4784,7 @@ DC01AF2128411D8400903101 /* ThemeableCollectionViewListCell.swift in Sources */, DC60F2A629802ABE00905EC8 /* UINavigationItem+NavigationContent.swift in Sources */, DCB1B99929D187B400BFF393 /* ThemeCSSButton.swift in Sources */, + DC8C07262CF9D6CC007B70E6 /* SearchScopeDescriptor.swift in Sources */, 399725E1233DF39300FC3B94 /* Calendar+Extension.swift in Sources */, DC298C9E2934D6D9009FA87F /* AccountAuthenticationUpdater.swift in Sources */, DC2A8E6A2B57EA8F001F0522 /* AccountControllerSearchViewController.swift in Sources */, @@ -4880,6 +4887,7 @@ DC66A9F6279EEC3100792AC8 /* ResourceViewHost.swift in Sources */, DC65592E28A644E10003D130 /* SearchTokenizer.swift in Sources */, DC24E0F828B41694002E4F5B /* OCQueryCondition+SearchToken.swift in Sources */, + DC8C072B2CF9DCC9007B70E6 /* SearchScope+Registry.swift in Sources */, 399EA73A25E656A900B6FF11 /* UITableView+Extension.swift in Sources */, DC46F3C72844A75200038880 /* OCDataItem+InteractionProtocols.swift in Sources */, DCCE1BFA28CA45D90098E3FE /* ItemSearchSuggestionsViewController.swift in Sources */, diff --git a/ownCloudAppShared/Client/Search/Item Search/Scopes/AccountSearchScope.swift b/ownCloudAppShared/Client/Search/Item Search/Scopes/AccountSearchScope.swift index 818ea8018..128c854e9 100644 --- a/ownCloudAppShared/Client/Search/Item Search/Scopes/AccountSearchScope.swift +++ b/ownCloudAppShared/Client/Search/Item Search/Scopes/AccountSearchScope.swift @@ -150,6 +150,15 @@ open class CustomQuerySearchScope : ItemSearchScope { // Subclasses open class AccountSearchScope : CustomQuerySearchScope { + open override class var descriptor: SearchScopeDescriptor { + return SearchScopeDescriptor(identifier: "account", title: OCLocalizedString("Account", nil), icon: OCSymbol.icon(forSymbolName: "person"), searchableContent: .itemName, scopeCreator: { (clientContext, cellStyle, descriptor) in + if let cellStyle { + return AccountSearchScope(with: clientContext, cellStyle: cellStyle, localizedName: descriptor.title, localizedPlaceholder: OCLocalizedString("Search account", nil), icon: descriptor.icon) + } + return nil + }) + } + public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, localizedPlaceholder placeholder: String? = nil, icon: UIImage? = nil) { var revealCellStyle : CollectionViewCellStyle? @@ -172,6 +181,19 @@ open class AccountSearchScope : CustomQuerySearchScope { } open class DriveSearchScope : AccountSearchScope { + open override class var descriptor: SearchScopeDescriptor { + return SearchScopeDescriptor(identifier: "drive", title: OCLocalizedString("Space", nil), icon: OCSymbol.icon(forSymbolName: "square.grid.2x2"), searchableContent: .itemName, scopeCreator: { (clientContext, cellStyle, descriptor) in + if let cellStyle, clientContext.query?.queryLocation != nil { + var placeholder = OCLocalizedString("Search space", nil) + if let driveName = clientContext.drive?.name, driveName.count > 0 { + placeholder = OCLocalizedFormat("Search {{space.name}}", ["space.name" : driveName]) + } + return DriveSearchScope(with: clientContext, cellStyle: cellStyle, localizedName: descriptor.title, localizedPlaceholder: placeholder, icon: descriptor.icon) + } + return nil + }) + } + private var driveID : String? public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, localizedPlaceholder placeholder: String? = nil, icon: UIImage? = nil) { @@ -203,6 +225,19 @@ open class DriveSearchScope : AccountSearchScope { } open class ContainerSearchScope: AccountSearchScope { + open override class var descriptor: SearchScopeDescriptor { + return SearchScopeDescriptor(identifier: "tree", title: OCLocalizedString("Tree", nil), icon: OCSymbol.icon(forSymbolName: "square.stack.3d.up"), searchableContent: .itemName, scopeCreator: { (clientContext, cellStyle, descriptor) in + if let cellStyle, clientContext.query?.queryLocation != nil { + var placeholder = OCLocalizedString("Search tree", nil) + if let path = clientContext.query?.queryLocation?.lastPathComponent, path.count > 0 { + placeholder = OCLocalizedFormat("Search from {{folder.name}}", ["folder.name" : path]) + } + return ContainerSearchScope(with: clientContext, cellStyle: cellStyle, localizedName: descriptor.title, localizedPlaceholder: placeholder, icon: descriptor.icon) + } + return nil + }) + } + private var location : OCLocation? public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, localizedPlaceholder placeholder: String? = nil, icon: UIImage? = nil) { @@ -246,3 +281,9 @@ open class ContainerSearchScope: AccountSearchScope { } } + +public extension SearchScopeDescriptor { + static var tree = ContainerSearchScope.descriptor + static var drive = DriveSearchScope.descriptor + static var account = AccountSearchScope.descriptor +} diff --git a/ownCloudAppShared/Client/Search/Item Search/Scopes/ServerSideSearchScope.swift b/ownCloudAppShared/Client/Search/Item Search/Scopes/ServerSideSearchScope.swift index 2e017374f..293283b07 100644 --- a/ownCloudAppShared/Client/Search/Item Search/Scopes/ServerSideSearchScope.swift +++ b/ownCloudAppShared/Client/Search/Item Search/Scopes/ServerSideSearchScope.swift @@ -21,6 +21,15 @@ import ownCloudSDK import ownCloudApp class ServerSideSearchScope: ItemSearchScope { + open override class var descriptor: SearchScopeDescriptor { + return SearchScopeDescriptor(identifier: "server", title: OCLocalizedString("Server", nil), icon: OCSymbol.icon(forSymbolName: "server.rack"), searchableContent: [.itemName, .contents], scopeCreator: { (clientContext, cellStyle, descriptor) in + if let cellStyle { + return ServerSideSearchScope(with: clientContext, cellStyle: cellStyle, localizedName: descriptor.title, localizedPlaceholder: OCLocalizedString("Search server", nil), icon: descriptor.icon) + } + return nil + }) + } + var searchResult: OCSearchResult? override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, localizedPlaceholder placeholder: String? = nil, icon: UIImage? = nil) { @@ -81,3 +90,7 @@ class ServerSideSearchScope: ItemSearchScope { kqlQuery = queryCondition?.kqlStringWithTypeAlias(toKQLTypeMap: OCQueryCondition.typeAliasToKeywordMap, targetContent: searchedContent) } } + +public extension SearchScopeDescriptor { + static var server = ServerSideSearchScope.descriptor +} diff --git a/ownCloudAppShared/Client/Search/Item Search/Scopes/SingleFolderSearchScope.swift b/ownCloudAppShared/Client/Search/Item Search/Scopes/SingleFolderSearchScope.swift index 03869a3c4..d14bc2bf1 100644 --- a/ownCloudAppShared/Client/Search/Item Search/Scopes/SingleFolderSearchScope.swift +++ b/ownCloudAppShared/Client/Search/Item Search/Scopes/SingleFolderSearchScope.swift @@ -67,6 +67,15 @@ open class QueryModifyingSearchScope : ItemSearchScope { // Subclass open class SingleFolderSearchScope : QueryModifyingSearchScope { + open override class var descriptor: SearchScopeDescriptor { + return SearchScopeDescriptor(identifier: "folder", title: OCLocalizedString("Folder", nil), icon: OCSymbol.icon(forSymbolName: "folder"), searchableContent: .itemName, scopeCreator: { (clientContext, cellStyle, descriptor) in + if clientContext.query?.queryLocation != nil { + return SingleFolderSearchScope(with: clientContext, cellStyle: nil, localizedName: descriptor.title, localizedPlaceholder: OCLocalizedString("Search folder", nil), icon: descriptor.icon) + } + return nil + }) + } + open override var savedSearchScope: OCSavedSearchScope? { return .folder } @@ -94,3 +103,7 @@ open class SingleFolderSearchScope : QueryModifyingSearchScope { return nil } } + +public extension SearchScopeDescriptor { + static var folder = SingleFolderSearchScope.descriptor +} diff --git a/ownCloudAppShared/Client/Search/Scopes/SearchScope+Registry.swift b/ownCloudAppShared/Client/Search/Scopes/SearchScope+Registry.swift new file mode 100644 index 000000000..2af3fd3ed --- /dev/null +++ b/ownCloudAppShared/Client/Search/Scopes/SearchScope+Registry.swift @@ -0,0 +1,38 @@ +// +// SearchScope+Registry.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 29.11.24. +// Copyright © 2024 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2024, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +extension SearchScope { + static var preferredSearchedContent: OCKQLSearchedContent? + + static func availableScopes(for clientContext: ClientContext, cellStyle: CollectionViewCellStyle) -> [SearchScope] { + var scopes : [SearchScope] = [] + + let descriptors = SearchScopeDescriptor.all + + for descriptor in descriptors { + if let scope = descriptor.createSearchScope(clientContext, cellStyle) { + scopes.append(scope) + } + } + + return scopes + } +} diff --git a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift index defc6f69a..169220085 100644 --- a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift +++ b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift @@ -20,6 +20,8 @@ import UIKit import ownCloudSDK open class SearchScope: NSObject, SearchElementUpdating { + open class var descriptor: SearchScopeDescriptor? { return nil } + public var localizedName : String public var localizedPlaceholder: String? public var icon : UIImage? @@ -89,34 +91,6 @@ open class SearchScope: NSObject, SearchElementUpdating { // MARK: - Convenience methods extension SearchScope { - static public func modifyingQuery(with context: ClientContext, localizedName: String) -> SearchScope { - return SingleFolderSearchScope(with: context, cellStyle: nil, localizedName: localizedName, localizedPlaceholder: OCLocalizedString("Search folder", nil), icon: OCSymbol.icon(forSymbolName: "folder")) - } - - static public func driveSearch(with context: ClientContext, cellStyle: CollectionViewCellStyle, localizedName: String) -> SearchScope { - var placeholder = OCLocalizedString("Search space", nil) - if let driveName = context.drive?.name, driveName.count > 0 { - placeholder = OCLocalizedFormat("Search {{space.name}}", ["space.name" : driveName]) - } - return DriveSearchScope(with: context, cellStyle: cellStyle, localizedName: localizedName, localizedPlaceholder: placeholder, icon: OCSymbol.icon(forSymbolName: "square.grid.2x2")) - } - - static public func containerSearch(with context: ClientContext, cellStyle: CollectionViewCellStyle, localizedName: String) -> SearchScope { - var placeholder = OCLocalizedString("Search tree", nil) - if let path = context.query?.queryLocation?.lastPathComponent, path.count > 0 { - placeholder = OCLocalizedFormat("Search from {{folder.name}}", ["folder.name" : path]) - } - return ContainerSearchScope(with: context, cellStyle: cellStyle, localizedName: localizedName, localizedPlaceholder: placeholder, icon: OCSymbol.icon(forSymbolName: "square.stack.3d.up")) - } - - static public func accountSearch(with context: ClientContext, cellStyle: CollectionViewCellStyle, localizedName: String) -> SearchScope { - return AccountSearchScope(with: context, cellStyle: cellStyle, localizedName: localizedName, localizedPlaceholder: OCLocalizedString("Search account", nil), icon: OCSymbol.icon(forSymbolName: "person")) - } - - static public func serverSideSearch(with context: ClientContext, cellStyle: CollectionViewCellStyle, localizedName: String) -> SearchScope { - return ServerSideSearchScope(with: context, cellStyle: cellStyle, localizedName: localizedName, localizedPlaceholder: OCLocalizedString("Search server", nil), icon: OCSymbol.icon(forSymbolName: "server.rack")) - } - static public func recipientSearch(with context: ClientContext, cellStyle: CollectionViewCellStyle, item: OCItem, localizedName: String) -> SearchScope { return RecipientSearchScope(with: context, cellStyle: cellStyle, item: item, localizedName: localizedName, localizedPlaceholder: OCLocalizedString("Search for users or groups", nil), icon: OCSymbol.icon(forSymbolName: "person.circle")) } diff --git a/ownCloudAppShared/Client/Search/Scopes/SearchScopeDescriptor.swift b/ownCloudAppShared/Client/Search/Scopes/SearchScopeDescriptor.swift new file mode 100644 index 000000000..2162e370d --- /dev/null +++ b/ownCloudAppShared/Client/Search/Scopes/SearchScopeDescriptor.swift @@ -0,0 +1,47 @@ +// +// SearchScopeDescriptor.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 29.11.24. +// Copyright © 2024 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2024, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +public struct SearchScopeDescriptor { + var identifier: String + + var title: String + var icon: UIImage? + + var searchableContent: OCKQLSearchedContent + + var scopeCreator: (_ clientContext: ClientContext, _ cellStyle: CollectionViewCellStyle?, _ descriptor: SearchScopeDescriptor) -> SearchScope? + + func createSearchScope(_ clientContext: ClientContext, _ cellStyle: CollectionViewCellStyle?) -> SearchScope? { + return self.scopeCreator(clientContext, cellStyle, self) + } +} + +public extension SearchScopeDescriptor { + static var all: [SearchScopeDescriptor] { + return [ + .folder, + .tree, + .drive, + .account, + .server + ] + } +} diff --git a/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift index 6263a938e..8949fcfec 100644 --- a/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift @@ -1019,33 +1019,7 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, open var searchViewController: SearchViewController? open func searchScopes(for clientContext: ClientContext, cellStyle: CollectionViewCellStyle) -> [SearchScope] { - var scopes : [SearchScope] = [] - - if clientContext.query?.queryLocation != nil { - // - Folder - // - Tree (folder + subfolders) - scopes.append(contentsOf: [ - // - In this folder - .modifyingQuery(with: clientContext, localizedName: OCLocalizedString("Folder", nil)), - - // - Folder and subfolders (tree / container) - .containerSearch(with: clientContext, cellStyle: cellStyle, localizedName: OCLocalizedString("Tree", nil)) - ]) - - // - Drive - if clientContext.core?.useDrives == true { - let driveName = OCLocalizedString("Space", nil) - scopes.append(.driveSearch(with: clientContext, cellStyle: cellStyle, localizedName: driveName)) - } - } - - // - Account - scopes.append(.accountSearch(with: clientContext, cellStyle: cellStyle, localizedName: OCLocalizedString("Account", nil))) - - // - Server - scopes.append(.serverSideSearch(with: clientContext, cellStyle: cellStyle, localizedName: OCLocalizedString("Server", nil))) - - return scopes + return SearchScope.availableScopes(for: clientContext, cellStyle: cellStyle) } @objc open func startSearch() {