Skip to content

Commit

Permalink
- SearchScopeDescriptor: new type to describe search scopes and insta…
Browse files Browse the repository at this point in the history
…ntiate 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
  • Loading branch information
felix-schwarz committed Nov 29, 2024
1 parent 944cea0 commit 1ef2af0
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 55 deletions.
8 changes: 8 additions & 0 deletions ownCloud.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -1417,6 +1419,8 @@
DC89EA6829958DF500BFF393 /* UIViewController+BrowserNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+BrowserNavigation.swift"; sourceTree = "<group>"; };
DC89EA6A29959BD200BFF393 /* AppStateActionRestoreNavigationBookmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateActionRestoreNavigationBookmark.swift; sourceTree = "<group>"; };
DC8AA7BD29DC154400BFF393 /* ItemLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemLayout.swift; sourceTree = "<group>"; };
DC8C07252CF9D6CC007B70E6 /* SearchScopeDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchScopeDescriptor.swift; sourceTree = "<group>"; };
DC8C072A2CF9DCC9007B70E6 /* SearchScope+Registry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchScope+Registry.swift"; sourceTree = "<group>"; };
DC8E99DB297E79E900594697 /* BrowserNavigationHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserNavigationHistory.swift; sourceTree = "<group>"; };
DC8E99DE297E8D3800594697 /* OCLicenseQAProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseQAProvider.h; sourceTree = "<group>"; };
DC8E99DF297E8D3800594697 /* OCLicenseQAProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCLicenseQAProvider.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3064,6 +3068,8 @@
isa = PBXGroup;
children = (
DCB5D5A628632C17004AF425 /* SearchScope.swift */,
DC8C07252CF9D6CC007B70E6 /* SearchScopeDescriptor.swift */,
DC8C072A2CF9DCC9007B70E6 /* SearchScope+Registry.swift */,
);
path = Scopes;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -81,3 +90,7 @@ class ServerSideSearchScope: ItemSearchScope {
kqlQuery = queryCondition?.kqlStringWithTypeAlias(toKQLTypeMap: OCQueryCondition.typeAliasToKeywordMap, targetContent: searchedContent)
}
}

public extension SearchScopeDescriptor {
static var server = ServerSideSearchScope.descriptor
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -94,3 +103,7 @@ open class SingleFolderSearchScope : QueryModifyingSearchScope {
return nil
}
}

public extension SearchScopeDescriptor {
static var folder = SingleFolderSearchScope.descriptor
}
38 changes: 38 additions & 0 deletions ownCloudAppShared/Client/Search/Scopes/SearchScope+Registry.swift
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/gpl-3.0.en.html>.
*
*/

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
}
}
30 changes: 2 additions & 28 deletions ownCloudAppShared/Client/Search/Scopes/SearchScope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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"))
}
Expand Down
47 changes: 47 additions & 0 deletions ownCloudAppShared/Client/Search/Scopes/SearchScopeDescriptor.swift
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/gpl-3.0.en.html>.
*
*/

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
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 1ef2af0

Please sign in to comment.