Skip to content

Commit

Permalink
- update SDK to include version tracking change for KVO data source
Browse files Browse the repository at this point in the history
- OCSavedSearch: add uuid and name to .dataItemVersion
- OCVault+SavedSearches: add method updateSavedSearch for updating existing saved searches (f.ex. with a different name)
- AccountController:
	- rename searchesFolder to globalSearch to signal the new position in the sidebar
	- present saved searches directly in the top level of the sidebar
	- add useFolderForSearches var to allow switching between a flat or folder-based presentation of saved searches in the sidebar	- cleanup code
- AccountControllerSearchViewController: remove added quick access searches from quick access list, remove entire Quick Access section if all quick access searches were added
- SavedSearchCell: use gear-badged folder instead of gear icon for saved searches in the sidebar
- CollectionViewController: preserve selection after non-animated snapshot updates
- OCSavedSearch+Interactions: add "Rename" action to saved search context menu
- ClientItemViewController: fix warning
  • Loading branch information
felix-schwarz committed Feb 8, 2024
1 parent 0f94a2c commit ea74eac
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 47 deletions.
2 changes: 1 addition & 1 deletion ownCloudAppFramework/Search/Saved Searches/OCSavedSearch.m
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ - (OCDataItemReference)dataItemReference

- (OCDataItemVersion)dataItemVersion
{
return (@(self.hash));
return ([NSString stringWithFormat:@"%lu%@%@", self.hash, self.name, self.uuid]);
}

#pragma mark - Comparison
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
@property(readonly,strong,nullable) NSArray<OCSavedSearch *> *savedSearches;

- (void)addSavedSearch:(OCSavedSearch *)savedSearch;
- (void)updateSavedSearch:(OCSavedSearch *)savedSearch;
- (void)deleteSavedSearch:(OCSavedSearch *)savedSearch;

- (void)addSavedSearchesObserver:(id)owner withInitial:(BOOL)initial updateHandler:(void(^)(id owner, NSArray<OCSavedSearch *> * _Nullable savedSearches, BOOL initial))updateHandler;
Expand Down
27 changes: 27 additions & 0 deletions ownCloudAppFramework/Search/Saved Searches/OCVault+SavedSearches.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,33 @@ - (void)addSavedSearch:(OCSavedSearch *)savedSearch
[self didChangeValueForKey:@"savedSearches"];
}

- (void)updateSavedSearch:(OCSavedSearch *)savedSearch
{
[self willChangeValueForKey:@"savedSearches"];

[self.keyValueStore updateObjectForKey:OCKeyValueStoreKeySavedSearches usingModifier:^id _Nullable(id _Nullable existingObject, BOOL * _Nonnull outDidModify) {
NSMutableArray<OCSavedSearch *> *savedSearches = OCTypedCast(existingObject, NSMutableArray);
NSUInteger countBefore;

if (savedSearches != nil)
{
NSUInteger existingOffset = [savedSearches indexOfObjectPassingTest:^BOOL(OCSavedSearch * _Nonnull existingSearch, NSUInteger idx, BOOL * _Nonnull stop) {
return ([existingSearch.uuid isEqual:savedSearch.uuid]);
}];

if (existingOffset != NSNotFound)
{
[savedSearches replaceObjectAtIndex:existingOffset withObject:savedSearch];
*outDidModify = YES;
}
}

return (savedSearches);
}];

[self didChangeValueForKey:@"savedSearches"];
}

- (void)deleteSavedSearch:(OCSavedSearch *)savedSearch
{
[self willChangeValueForKey:@"savedSearches"];
Expand Down
57 changes: 28 additions & 29 deletions ownCloudAppShared/Client/Account/Controller/AccountController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public class AccountController: NSObject, OCDataItem, OCDataItemVersioning, Acco

case spacesFolder

case searchesFolder
case globalSearch

case recents
case favoriteItems
Expand Down Expand Up @@ -190,11 +190,9 @@ public class AccountController: NSObject, OCDataItem, OCDataItemVersioning, Acco
if let vault = connection.core?.vault {
// Create savedSearchesDataSource if wanted
if configuration.showSearch, savedSearchesDataSource == nil {
savedSearchesDataSource = OCDataSourceKVO(object: vault, keyPath: "savedSearches", versionedItemUpdateHandler: { [weak self] obj, keypath, newValue in
savedSearchesDataSource = OCDataSourceKVO(object: vault, keyPath: "savedSearches", versionedItemUpdateHandler: { obj, keypath, newValue in
if let savedSearches = newValue as? [OCSavedSearch] {
let searches = savedSearches.filter { savedSearch in return !savedSearch.isTemplate }
// self?.savedSearchesVisible = searches.count > 0
return searches
return savedSearches.filter { savedSearch in return !savedSearch.isTemplate }
}

return nil
Expand Down Expand Up @@ -279,13 +277,6 @@ public class AccountController: NSObject, OCDataItem, OCDataItemVersioning, Acco
@objc dynamic var showDisconnectButton: Bool = false

var savedSearchesDataSource: OCDataSourceKVO?
// var savedSearchesVisible: Bool = true {
// didSet {
// if oldValue != savedSearchesVisible, let savedSearchesFolderDatasource = specialItemsDataSources[.searchesFolder] {
// itemsDataSource.setInclude(savedSearchesVisible, for: savedSearchesFolderDatasource)
// }
// }
// }
var savedSearchesCondition: DataSourceCondition?

open var specialItems: [SpecialItem : OCDataItem & OCDataItemVersioning] = [:]
Expand Down Expand Up @@ -320,6 +311,8 @@ public class AccountController: NSObject, OCDataItem, OCDataItemVersioning, Acco

private var legacyAccountRootLocation: OCLocation

private let useFolderForSearches: Bool = false

func composeItemsDataSource() {
if let core = connection?.core {
var sources : [OCDataSource] = []
Expand Down Expand Up @@ -384,21 +377,27 @@ public class AccountController: NSObject, OCDataItem, OCDataItemVersioning, Acco
}

// Saved searches
if configuration.showSearch, let savedSearchesDataSource {
// savedSearchesCondition = DataSourceCondition(.empty, with: savedSearchesDataSource, initial: true, action: { [weak self] condition in
// self?.savedSearchesVisible = condition.fulfilled == false
// })
var savedSearchesSidebarDataSource: OCDataSource?

let savedSearchesFolderDataSource = self.buildTopFolder(with: savedSearchesDataSource, title: "Search".localized, icon: OCSymbol.icon(forSymbolName: "magnifyingglass"), topItem: .searchesFolder) { [weak self] context, action in
return self?.provideViewController(for: .searchesFolder, in: context)
}
if configuration.showSearch, let savedSearchesDataSource {
if useFolderForSearches {
// Use "Search" item in sidebar, showing saved searches when unfolded
let savedSearchesFolderDataSource = self.buildTopFolder(with: savedSearchesDataSource, title: "Search".localized, icon: OCSymbol.icon(forSymbolName: "magnifyingglass"), topItem: .globalSearch) { [weak self] context, action in
return self?.provideViewController(for: .globalSearch, in: context)
}

// let (savedSearchesFolderDataSource, savedSearchesFolderItem) = self.buildFolder(with: savedSearchesDataSource, title: "Search".localized, icon: OCSymbol.icon(forSymbolName: "magnifyingglass"), folderItemRef:specialItemsDataReferences[.searchesFolder]!)
sources.append(savedSearchesFolderDataSource)
} else {
// Add "Search" item to sidebar, making saved searches standalone items
let globalSearchItem = CollectionSidebarAction(with: "Search".localized, icon: OCSymbol.icon(forSymbolName: "magnifyingglass"), identifier: specialItemsDataReferences[.globalSearch], viewControllerProvider: { [weak self] context, action in
return self?.provideViewController(for: .globalSearch, in: context)
}, cacheViewControllers: false)

// specialItems[.searchesFolder] = savedSearchesFolderItem
// specialItemsDataSources[.searchesFolder] = savedSearchesFolderDataSource
specialItems[.globalSearch] = globalSearchItem

sources.append(savedSearchesFolderDataSource)
sources.append(OCDataSourceArray(items: [ globalSearchItem ]))
savedSearchesSidebarDataSource = savedSearchesDataSource // Add saved searches only after Available Offline
}
}

// Other sidebar items
Expand Down Expand Up @@ -445,6 +444,11 @@ public class AccountController: NSObject, OCDataItem, OCDataItemVersioning, Acco
sources.append(otherItemsDataSource)
}

// Saved searches (if not in folder)
if let savedSearchesSidebarDataSource {
sources.append(savedSearchesSidebarDataSource)
}

// Extra items (Activity & Co via class extension in the app)
if let extraItemsSupport = self as? AccountControllerExtraItems {
extraItemsSupport.updateExtraItems(dataSource: extraItemsDataSource)
Expand All @@ -453,10 +457,6 @@ public class AccountController: NSObject, OCDataItem, OCDataItemVersioning, Acco
}

itemsDataSource.sources = sources

// if let savedSearchesFolderDataSource = specialItemsDataSources[.searchesFolder], !savedSearchesVisible {
// itemsDataSource.setInclude(savedSearchesVisible, for: savedSearchesFolderDataSource)
// }
}
}

Expand Down Expand Up @@ -518,7 +518,7 @@ public class AccountController: NSObject, OCDataItem, OCDataItemVersioning, Acco
case .spacesFolder:
viewController = AccountControllerSpacesGridViewController(with: context)

case .searchesFolder:
case .globalSearch:
viewController = AccountControllerSearchViewController(context: context)

case .availableOfflineItems:
Expand Down Expand Up @@ -628,7 +628,6 @@ extension AccountController: DataItemSelectionInteraction {
let /* spacesFolderItemRef */ _ = section?.collectionViewController?.wrap(references: [specialItemsDataReferences[.spacesFolder]!], forSection: sectionID).first {
section?.collectionViewController?.addActions([
CollectionViewAction(kind: .select(animated: false, scrollPosition: .centeredVertically), itemReference: personalFolderItemRef)
// CollectionViewAction(kind: .expand(animated: true), itemReference: spacesFolderItemRef)
])
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,32 @@ class AccountControllerSearchViewController: ClientItemViewController {
override func composeSuggestionContents(from savedSearches: [OCSavedSearch]?, clientContext: ClientContext, includingFallbacks: Bool) -> [OCDataItem & OCDataItemVersioning] {
var suggestions = super.composeSuggestionContents(from: savedSearches, clientContext: clientContext, includingFallbacks: false)

let headerView = ComposedMessageView.sectionHeader(titled: "Quick Access".localized)
headerView.elementInsets = .zero
suggestions.insert(headerView, at: 0)
let savedSearches = clientContext.core?.vault.savedSearches ?? []
var thinnedQuickAccessSuggestions : [OCSavedSearch] = []

suggestions.insert(contentsOf: quickAccessSuggestions, at: 1)
for quickAccessSuggestion in quickAccessSuggestions {
let storedSearch = savedSearches.first(where: { storedSavedSearch in
return storedSavedSearch.searchTerm == quickAccessSuggestion.searchTerm &&
storedSavedSearch.customIconName == quickAccessSuggestion.customIconName &&
storedSavedSearch.name == quickAccessSuggestion.name &&
storedSavedSearch.isTemplate != quickAccessSuggestion.isTemplate &&
storedSavedSearch.isQuickAccess == quickAccessSuggestion.isQuickAccess &&
storedSavedSearch.useNameAsTitle == quickAccessSuggestion.useNameAsTitle &&
storedSavedSearch.scope == quickAccessSuggestion.scope
})

if storedSearch == nil {
thinnedQuickAccessSuggestions.append(quickAccessSuggestion)
}
}

if thinnedQuickAccessSuggestions.count > 0 {
let headerView = ComposedMessageView.sectionHeader(titled: "Quick Access".localized)
headerView.elementInsets = .zero
suggestions.insert(headerView, at: 0)

suggestions.insert(contentsOf: thinnedQuickAccessSuggestions, at: 1)
}

return suggestions
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ extension OCSavedSearch {

extension SavedSearchCell {
static let savedTemplateIcon = OCSymbol.icon(forSymbolName: "square.dashed.inset.filled")
static let savedSearchIcon = OCSymbol.icon(forSymbolName: "gearshape.fill")
static let savedSearchIcon = OCSymbol.icon(forSymbolName: "folder.badge.gearshape")

static func registerCellProvider() {
let savedSearchCellRegistration = UICollectionView.CellRegistration<SavedSearchCell, CollectionViewController.ItemRef> { (cell, indexPath, collectionItemRef) in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,17 @@ open class CollectionViewController: UIViewController, UICollectionViewDelegate,
var snapshot = NSDiffableDataSourceSnapshot<CollectionViewSection.SectionIdentifier, CollectionViewController.ItemRef>()
var snapshotsBySection = [CollectionViewSection.SectionIdentifier : NSDiffableDataSourceSectionSnapshot<CollectionViewController.ItemRef>]()
var updatedItems : [CollectionViewController.ItemRef] = []
var selectedItemRefs: [ItemRef]?

let updateWithAnimation = animatingDifferences && !useWrappedIdentifiers

if !updateWithAnimation {
// Selection is lost when updating without animation (via https://forums.developer.apple.com/forums/thread/656529?answerId=627227022#627227022)
let selectedIndexPaths = collectionView.indexPathsForSelectedItems
selectedItemRefs = selectedIndexPaths?.compactMap({ indexPath in
return collectionViewDataSource.itemIdentifier(for: indexPath)
})
}

// Log.debug("<=========================>")

Expand All @@ -541,7 +552,7 @@ open class CollectionViewController: UIViewController, UICollectionViewDelegate,
}
}

collectionViewDataSource.apply(snapshot, animatingDifferences: animatingDifferences && !useWrappedIdentifiers)
collectionViewDataSource.apply(snapshot, animatingDifferences: updateWithAnimation)

if useWrappedIdentifiers {
for section in sections {
Expand All @@ -563,6 +574,23 @@ open class CollectionViewController: UIViewController, UICollectionViewDelegate,
}
}

// Restore selected item refs
if let selectedItemRefs {
var selectIndexPaths: [IndexPath]?

selectIndexPaths = selectedItemRefs.compactMap({ itemRef in
collectionViewDataSource.indexPath(for: itemRef)
})

if let selectIndexPaths, selectIndexPaths.count > 0 {
OnMainThread {
for selectedIndexPath in selectIndexPaths {
self.collectionView.selectItem(at: selectedIndexPath, animated: false, scrollPosition: .centeredVertically)
}
}
}
}

// Notify view controller of content updates
setContentDidUpdate()
}
Expand Down Expand Up @@ -998,7 +1026,7 @@ open class CollectionViewController: UIViewController, UICollectionViewDelegate,
return nil
}

if let menuItems = contextMenuInteraction.composeContextMenuItems(in: self, location: .contextMenuItem, with: self.clientContext) {
if let menuItems = contextMenuInteraction.composeContextMenuItems(in: self, location: .contextMenuItem, with: clientContext) {
return UIMenu(title: "", children: menuItems)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ extension OCSavedSearch {
})
}

func canRename(in context: ClientContext?) -> Bool {
return canDelete(in: context) && (isQuickAccess != true)
}

func delete(in context: ClientContext?) {
guard let context = context, let core = context.core else {
return
Expand All @@ -46,6 +50,31 @@ extension OCSavedSearch {
core.vault.delete(self)
}

func rename(in context: ClientContext?) {
guard let context = context, context.core != nil else {
return
}

let namePrompt = UIAlertController(title: "Name of saved search".localized, message: nil, preferredStyle: .alert)

namePrompt.addTextField(configurationHandler: { textField in
textField.placeholder = "Saved search".localized
textField.text = self.isNameUserDefined ? self.name : ""
})

namePrompt.addAction(UIAlertAction(title: "Cancel".localized, style: .cancel))
namePrompt.addAction(UIAlertAction(title: "Save".localized, style: .default, handler: { [weak self, weak namePrompt] action in
guard let self else {
return
}
self.name = namePrompt?.textFields?.first?.text ?? ""

context.core?.vault.update(self)
}))

context.present(namePrompt, animated: true)
}

func condition() -> OCQueryCondition? {
let searchTermCondition = OCQueryCondition.fromSearchTerm(searchTerm)
var composedCondition = searchTermCondition
Expand Down Expand Up @@ -230,18 +259,35 @@ extension OCSavedSearch: DataItemSwipeInteraction {

extension OCSavedSearch: DataItemContextMenuInteraction {
public func composeContextMenuItems(in viewController: UIViewController?, location: OCExtensionLocationIdentifier, with context: ClientContext?) -> [UIMenuElement]? {
guard canDelete(in: context) else {
let canDelete = canDelete(in: context), canRename = canRename(in: context)
var actions: [UIMenuElement] = []

guard canDelete || canRename else {
return nil
}

let deleteAction = UIAction(handler: { [weak self] action in
self?.delete(in: context)
})
deleteAction.title = "Delete".localized
deleteAction.image = OCSymbol.icon(forSymbolName: "trash")
deleteAction.attributes = .destructive
if canRename {
let renameAction = UIAction(handler: { [weak self] action in
self?.rename(in: context)
})
renameAction.title = "Rename".localized
renameAction.image = OCSymbol.icon(forSymbolName: "pencil")

actions.append(renameAction)
}

if canDelete {
let deleteAction = UIAction(handler: { [weak self] action in
self?.delete(in: context)
})
deleteAction.title = "Delete".localized
deleteAction.image = OCSymbol.icon(forSymbolName: "trash")
deleteAction.attributes = .destructive

actions.append(deleteAction)
}

return [ deleteAction ]
return actions
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1059,9 +1059,11 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate,

// Suggestion view
let suggestionsSource = OCDataSourceArray()
suggestionsSource.trackItemVersions = true

let suggestionsContent = SearchViewController.Content(type: .suggestion, source: suggestionsSource, style: emptySection!.cellStyle)

if let vault = clientContext.core?.vault {
if clientContext.core?.vault != nil {
startProvidingSearchSuggestions(to: suggestionsSource, in: clientContext)
}

Expand Down

0 comments on commit ea74eac

Please sign in to comment.