From 14aad901a80986eca9113a87ac4f245d32e370c3 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 21 Oct 2019 09:59:45 +0200 Subject: [PATCH 1/3] - Fix Swift and SwiftLint warnings - Remove unused UploadsSettingsSection (was replaced by MediaUploadSettings) --- ownCloud.xcodeproj/project.pbxproj | 4 - .../PhotoKit Extensions/PHAsset+Upload.swift | 2 +- .../Settings/UploadsSettingsSection.swift | 243 ------------------ ownCloud/UI Elements/StaticTableViewRow.swift | 2 +- 4 files changed, 2 insertions(+), 249 deletions(-) delete mode 100644 ownCloud/Settings/UploadsSettingsSection.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 412f89fff..d9317274c 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 2308F94321467F6200CF0B91 /* ClientDirectoryPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2308F93C21467F6200CF0B91 /* ClientDirectoryPickerViewController.swift */; }; 232B01F42126B0CE00366FA0 /* MoreViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232B01F32126B0CE00366FA0 /* MoreViewHeader.swift */; }; 232B01F62126B10900366FA0 /* MoreStaticTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232B01F52126B10900366FA0 /* MoreStaticTableViewController.swift */; }; - 232F7CAD2097140300EE22E4 /* UploadsSettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232F7CAC2097140300EE22E4 /* UploadsSettingsSection.swift */; }; 232F7CAF2097260400EE22E4 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232F7CAE2097260400EE22E4 /* SettingsViewController.swift */; }; 233BDEA0204FEFE500C06732 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233BDE9F204FEFE500C06732 /* AppDelegate.swift */; }; 233BDEA7204FEFE500C06732 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 233BDEA6204FEFE500C06732 /* Assets.xcassets */; }; @@ -583,7 +582,6 @@ 2308F93C21467F6200CF0B91 /* ClientDirectoryPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientDirectoryPickerViewController.swift; sourceTree = ""; }; 232B01F32126B0CE00366FA0 /* MoreViewHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreViewHeader.swift; sourceTree = ""; }; 232B01F52126B10900366FA0 /* MoreStaticTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreStaticTableViewController.swift; sourceTree = ""; }; - 232F7CAC2097140300EE22E4 /* UploadsSettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadsSettingsSection.swift; sourceTree = ""; }; 232F7CAE2097260400EE22E4 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 233BDE9C204FEFE500C06732 /* ownCloud.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ownCloud.app; sourceTree = BUILT_PRODUCTS_DIR; }; 233BDE9F204FEFE500C06732 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -1819,7 +1817,6 @@ 593BAB3F209ADFB900023634 /* Passcode */, 233E0FD72099F11D00C3D8D5 /* SecuritySettingsSection.swift */, DCD2D40522F06ECA0071FB8F /* StorageSettingsSection.swift */, - 232F7CAC2097140300EE22E4 /* UploadsSettingsSection.swift */, DC854935218331CF00782BA8 /* UserInterfaceSettingsSection.swift */, ); path = Settings; @@ -2594,7 +2591,6 @@ 4C88041822E78D790016CBA9 /* MediaFilesSettings.swift in Sources */, DC3BE0E12077CD4B002A0AC0 /* Synchronized.swift in Sources */, DCE5E8B82080D8D9005F60CE /* OCItem+Extension.swift in Sources */, - 232F7CAD2097140300EE22E4 /* UploadsSettingsSection.swift in Sources */, 4CB8ADDE22DF5D3700F1FEBC /* PHPhotoLibrary+Extension.swift in Sources */, DCB44D852186FEF700DAA4CC /* ThemeStyle+DefaultStyles.swift in Sources */, 597A404920AD59EF00B028B2 /* AppLockWindow.swift in Sources */, diff --git a/ownCloud/PhotoKit Extensions/PHAsset+Upload.swift b/ownCloud/PhotoKit Extensions/PHAsset+Upload.swift index ad95ee712..e7695f1dd 100644 --- a/ownCloud/PhotoKit Extensions/PHAsset+Upload.swift +++ b/ownCloud/PhotoKit Extensions/PHAsset+Upload.swift @@ -122,7 +122,7 @@ extension PHAsset { guard let url = assetURL else { return } - var fileName = url.lastPathComponent + let fileName = url.lastPathComponent // Check if the conversion was requested and current media format is not found in the list of requested formats if let formats = preferredFormats, formats.count > 0 { diff --git a/ownCloud/Settings/UploadsSettingsSection.swift b/ownCloud/Settings/UploadsSettingsSection.swift deleted file mode 100644 index f670a7a8a..000000000 --- a/ownCloud/Settings/UploadsSettingsSection.swift +++ /dev/null @@ -1,243 +0,0 @@ -// -// UploadsSettingsViewController.swift -// ownCloud -// -// Created by Pablo Carrascal on 30/04/2018. -// Copyright © 2018 ownCloud GmbH. All rights reserved. -// - -/* - * Copyright (C) 2018, 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 - -// MARK: Photo uploads user defaults key. -public let UploadsPhotoUploadKey: String = "uploads-settings-photos" -public let UploadsPhotoWifiOnlyKey: String = "uploads-settings-photos-wifi" -public let UploadsPhotoSelectedPathKey: String = "uploads-settings-photos-path" - -// MARK: Video uploads user defaults key. -public let UploadsVideoUploadKey: String = "uploads-settings-videos" -public let UploadsVideoWifiOnlyKey: String = "uploads-settings-videos-wifi" -public let UploadsVideoSelectedPathKey: String = "uploads-settings-videos-path" - -private let UploadsBackgroundUploadsKey: String = "uploads-settings-background" - -private let uploadsSelectedPath: String = "/cameraUpload" - -// MARK: - Section identifier -private let UploadsSectionIdentifier: String = "settings-uploads-section" - -class UploadsSettingsSection: SettingsSection { - - // MARK: Upload settings properties. - - /// Instant Photo Uploads are anabled. - private var isPhotoUploadEnabled: Bool { - willSet { - userDefaults.set(newValue, forKey: UploadsPhotoUploadKey) - photosRow?.value = newValue - } - - didSet { - updateUI() - } - } - /// Instant Photo Uploads only with WiFi connection. - private var isPhotoWifiOnlyUploadsEnabled: Bool { - willSet { - userDefaults.set(newValue, forKey: UploadsPhotoWifiOnlyKey) - photosWifiOnlyRow?.value = newValue - } - } - /// Path in which the photos are going to be instant uploaded - private var photoSelectedPath: String? { - willSet { - if newValue == nil { - userDefaults.removeObject(forKey: UploadsPhotoSelectedPathKey) - } else { - userDefaults.set(newValue, forKey: UploadsPhotoSelectedPathKey) - videosSelectedPathRow?.value = newValue - } - } - } - - /// Instant Video Uploads are anabled. - private var isVideoUploadEnabled: Bool { - willSet { - userDefaults.set(newValue, forKey: UploadsVideoUploadKey) - videosRow?.value = newValue - } - - didSet { - updateUI() - } - } - /// Instant Video Uploads only with WiFi connection. - private var isVideoWifiOnlyUploadsEnabled: Bool { - willSet { - userDefaults.set(newValue, forKey: UploadsVideoWifiOnlyKey) - videosWifiOnlyRow?.value = newValue - } - } - - /// Path in which the video are going to be instant uploaded - private var videoSelectedPath: String? { - willSet { - if newValue == nil { - userDefaults.removeObject(forKey: UploadsVideoSelectedPathKey) - } else { - userDefaults.set(newValue, forKey: UploadsVideoSelectedPathKey) - videosSelectedPathRow?.value = newValue - } - } - } - - /// Instant Background Uploads are anabled. - private var backgroundUploadsEnabled: Bool { - willSet { - userDefaults.set(newValue, forKey: UploadsBackgroundUploadsKey) - backgroundUploadsRow?.value = newValue - } - } - - // MARK: - Upload Settings Cells - - private var photosRow: StaticTableViewRow? - private var photosWifiOnlyRow: StaticTableViewRow? - private var photosSelectedPathRow: StaticTableViewRow? - - private var videosRow: StaticTableViewRow? - private var videosWifiOnlyRow: StaticTableViewRow? - private var videosSelectedPathRow: StaticTableViewRow? - - private var backgroundUploadsRow: StaticTableViewRow? - - override init(userDefaults: UserDefaults) { - - self.isPhotoUploadEnabled = userDefaults.bool(forKey: UploadsPhotoUploadKey) - self.isPhotoWifiOnlyUploadsEnabled = userDefaults.bool(forKey: UploadsPhotoWifiOnlyKey) - self.photoSelectedPath = userDefaults.string(forKey: UploadsPhotoSelectedPathKey) ?? uploadsSelectedPath - - self.isVideoUploadEnabled = userDefaults.bool(forKey: UploadsVideoUploadKey) - self.isVideoWifiOnlyUploadsEnabled = userDefaults.bool(forKey: UploadsVideoWifiOnlyKey) - self.videoSelectedPath = userDefaults.string(forKey: UploadsVideoSelectedPathKey) ?? uploadsSelectedPath - - self.backgroundUploadsEnabled = userDefaults.bool(forKey: UploadsBackgroundUploadsKey) - - super.init(userDefaults: userDefaults) - - createPhotoRows() - createVideoRows() - createCommonRows() - - self.headerTitle = "Instant Uploads".localized + " -- Work in progress" - self.identifier = UploadsSectionIdentifier - - self.add(rows: [photosRow!, videosRow!]) - updateUI() - } - - // MARK: - Creation of the rows. - - private func createPhotoRows() { - - photosRow = StaticTableViewRow(switchWithAction: { [weak self] (_, sender) in - if let photosSwitch = sender as? UISwitch { - self?.isPhotoUploadEnabled = photosSwitch.isOn - } - }, title: "Photos".localized, value: isPhotoUploadEnabled) - - photosWifiOnlyRow = StaticTableViewRow(switchWithAction: { [weak self] (_, sender) in - if let photosWifiOnlySwitch = sender as? UISwitch { - self?.isPhotoWifiOnlyUploadsEnabled = photosWifiOnlySwitch.isOn - } - }, title: "Upload pictures via WiFi only".localized, value: isPhotoWifiOnlyUploadsEnabled) - - photosSelectedPathRow = StaticTableViewRow(subtitleRowWithAction: { (_, _) in - // TODO: Use a more advanced version of ClientQueryViewController to select the path - }, title: "Photo upload path".localized, subtitle: photoSelectedPath, accessoryType: .disclosureIndicator) - } - - private func createVideoRows() { - - videosRow = StaticTableViewRow(switchWithAction: { [weak self] (_, sender) in - if let videosSwitch = sender as? UISwitch { - self?.isVideoUploadEnabled = videosSwitch.isOn - } - }, title: "Videos".localized, value: isVideoUploadEnabled) - - videosWifiOnlyRow = StaticTableViewRow(switchWithAction: { [weak self] (_, sender) in - if let videosWifiOnlySwitch = sender as? UISwitch { - self?.isVideoWifiOnlyUploadsEnabled = videosWifiOnlySwitch.isOn - } - }, title: "Upload videos via WiFi only".localized, value: isVideoWifiOnlyUploadsEnabled) - - videosSelectedPathRow = StaticTableViewRow(subtitleRowWithAction: { (_, _) in - // TODO: Use a more advanced version of ClientQueryViewController to select the path - }, title: "Video upload path".localized, subtitle: videoSelectedPath, accessoryType: .disclosureIndicator) - } - - private func createCommonRows() { - backgroundUploadsRow = StaticTableViewRow(switchWithAction: { [weak self] (_, sender) in - if let backgroundsSwitch = sender as? UISwitch { - self?.backgroundUploadsEnabled = backgroundsSwitch.isOn - } - }, title: "Background uploads".localized, value: backgroundUploadsEnabled) - } - - /// Create and configure all the rows the Uploads Section has in the settings view. - func updateUI() { - - if isPhotoUploadEnabled { - - let photoUploadIndex = rows.index(of: photosRow!)! - - if !rows.contains(photosWifiOnlyRow!) { - insert(row: photosWifiOnlyRow!, at: photoUploadIndex + 1, animated: true) - insert(row: photosSelectedPathRow!, at: photoUploadIndex + 2, animated: true) - } - - } else { - isPhotoWifiOnlyUploadsEnabled = false - remove(rows: [photosWifiOnlyRow!, photosSelectedPathRow!], animated: true) - videoSelectedPath = nil - } - - if isVideoUploadEnabled { - - let videoUploadIndex = rows.index(of: videosRow!)! - - if !rows.contains(videosWifiOnlyRow!) { - insert(row: videosWifiOnlyRow!, at: videoUploadIndex + 1, animated: true) - insert(row: videosSelectedPathRow!, at: videoUploadIndex + 2, animated: true) - } - - } else { - isVideoWifiOnlyUploadsEnabled = false - remove(rows: [videosWifiOnlyRow!, videosSelectedPathRow!], animated: true) - videoSelectedPath = nil - } - - // Background uploads flow - if isPhotoUploadEnabled || isVideoUploadEnabled { - - if !rows.contains(backgroundUploadsRow!) { - add(row: backgroundUploadsRow!, animated: true) - } - - } else { - backgroundUploadsEnabled = false - remove(rows: [backgroundUploadsRow!], animated: true) - } - } - -} diff --git a/ownCloud/UI Elements/StaticTableViewRow.swift b/ownCloud/UI Elements/StaticTableViewRow.swift index 25c200996..c593c3966 100644 --- a/ownCloud/UI Elements/StaticTableViewRow.swift +++ b/ownCloud/UI Elements/StaticTableViewRow.swift @@ -512,7 +512,7 @@ class StaticTableViewRow : NSObject, UITextFieldDelegate { // MARK: - Buttons - convenience init(buttonWithAction action: StaticTableViewRowAction?, title: String, style: StaticTableViewRowButtonStyle = .proceed, image: UIImage? = nil, imageWidth : CGFloat? = nil, imageTintColorKey : String? = nil, alignment: NSTextAlignment = .center, identifier : String? = nil, accessoryView: UIView? = nil) { + convenience init(buttonWithAction action: StaticTableViewRowAction?, title: String, style: StaticTableViewRowButtonStyle = .proceed, image: UIImage? = nil, imageWidth : CGFloat? = nil, imageTintColorKey : String? = nil, alignment: NSTextAlignment = .center, identifier : String? = nil, accessoryView: UIView? = nil) { self.init() type = .button From 5904205a58eb5a949d390381bf0ce403c55a117b Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 21 Oct 2019 12:58:49 +0200 Subject: [PATCH 2/3] - Update SDK to disable background NSURLSession usage in the app --- ios-sdk | 2 +- ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ios-sdk b/ios-sdk index c7e45dbd5..599686367 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit c7e45dbd517be0f3191e5567a929dd0302d29e00 +Subproject commit 59968636785aa078c233b8e178181bd116d09d36 diff --git a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme index 74d0af019..6d67db6c3 100644 --- a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme +++ b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme @@ -191,8 +191,8 @@ + value = "[BGMAN]" + isEnabled = "YES"> Date: Thu, 24 Oct 2019 21:47:18 +0200 Subject: [PATCH 3/3] Added global search support: - uses custom queries under the hood - add SearchScope enum - ClientQueryViewController changes - add customSearchQuery to act as data source during global search - omit "Total size" footer during search - allow switching between actual backing query and customSearchQuery - add "global | folder" switch, shown only when search is active - make MessageView leave the UITableView.tableHeaderViews unobscured --- .../Client/ClientQueryViewController.swift | 107 ++++++++++++++++- ownCloud/Client/SortBar.swift | 72 +++++++++++- .../QueryFileListTableViewController.swift | 108 ++++++++++-------- ownCloud/UI Elements/MessageView.swift | 8 +- 4 files changed, 234 insertions(+), 61 deletions(-) diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index f08ed0b01..e9f75cb85 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -35,7 +35,7 @@ extension OCQueryState { } } -class ClientQueryViewController: QueryFileListTableViewController, UIDropInteractionDelegate, UIPopoverPresentationControllerDelegate { +class ClientQueryViewController: QueryFileListTableViewController, UIDropInteractionDelegate, UIPopoverPresentationControllerDelegate, UISearchControllerDelegate { var selectedItemIds = Set() var actions : [Action]? @@ -58,8 +58,50 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac private var _actionProgressHandler : ActionProgressHandler? + private var _query : OCQuery + override var query : OCQuery { + set { + _query = newValue + } + + get { + if let customSearchQuery = customSearchQuery { + return customSearchQuery + } else { + return _query + } + } + } + var customSearchQuery : OCQuery? { + willSet { + if customSearchQuery != newValue, let query = customSearchQuery { + core?.stop(query) + query.delegate = nil + } + } + + didSet { + if customSearchQuery != nil, let query = customSearchQuery { + query.delegate = self + query.sortComparator = sortMethod.comparator(direction: sortDirection) + core?.start(query) + } + } + } + override var searchScope: SearchScope { + set { + UserDefaults.standard.setValue(newValue.rawValue, forKey: "search-scope") + } + + get { + let scope = SearchScope(rawValue: UserDefaults.standard.integer(forKey: "search-scope")) ?? SearchScope.local + return scope + } + } + // MARK: - Init & Deinit public override init(core inCore: OCCore, query inQuery: OCQuery) { + _query = inQuery super.init(core: inCore, query: inQuery) let lastPathComponent = (query.queryPath as NSString?)!.lastPathComponent @@ -126,6 +168,49 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac } } + // MARK: - Search events + func willPresentSearchController(_ searchController: UISearchController) { + self.sortBar?.showSearchScope = true + } + + func willDismissSearchController(_ searchController: UISearchController) { + self.sortBar?.showSearchScope = false + } + + // MARK: - Search scope support + private var searchText: String? + + override func applySearchFilter(for searchText: String?, to query: OCQuery) { + self.searchText = searchText + + updateCustomSearchQuery() + } + + override func sortBar(_ sortBar: SortBar, didUpdateSearchScope: SearchScope) { + updateCustomSearchQuery() + } + + override func sortBar(_ sortBar: SortBar, didUpdateSortMethod: SortMethod) { + sortMethod = didUpdateSortMethod + + let comparator = sortMethod.comparator(direction: sortDirection) + + _query.sortComparator = comparator + customSearchQuery?.sortComparator = comparator + } + + func updateCustomSearchQuery() { + if let searchText = searchText, let searchScope = sortBar?.searchScope, searchScope == .global { + self.customSearchQuery = OCQuery(condition: .where(.name, contains: searchText), inputFilter: nil) + } else { + self.customSearchQuery = nil + } + + super.applySearchFilter(for: searchText, to: _query) + + self.queryHasChangesAvailable(query) + } + // MARK: - View controller events override func viewDidLoad() { super.viewDidLoad() @@ -170,6 +255,12 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac private var viewControllerVisible : Bool = false + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + searchController?.delegate = self + } + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) @@ -588,10 +679,16 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac // MARK: - Updates override func performUpdatesWithQueryChanges(query: OCQuery, changeSet: OCQueryChangeSet?) { - if let rootItem = self.query.rootItem { - if query.queryPath != "/" { - let totalSize = String(format: "Total: %@".localized, rootItem.sizeLocalized) - self.updateFooter(text: totalSize) + if query == self.query { + super.performUpdatesWithQueryChanges(query: query, changeSet: changeSet) + + if let rootItem = self.query.rootItem, searchText == nil { + if query.queryPath != "/" { + let totalSize = String(format: "Total: %@".localized, rootItem.sizeLocalized) + self.updateFooter(text: totalSize) + } + } else { + self.updateFooter(text: nil) } } } diff --git a/ownCloud/Client/SortBar.swift b/ownCloud/Client/SortBar.swift index dd3f17506..2e8ddbb2b 100644 --- a/ownCloud/Client/SortBar.swift +++ b/ownCloud/Client/SortBar.swift @@ -35,13 +35,32 @@ class SegmentedControl: UISegmentedControl { } } +public enum SearchScope : Int, CaseIterable { + case global + case local + + var label : String { + var name : String! + + switch self { + case .global: name = "all".localized + case .local: name = "folder".localized + } + + return name + } +} + protocol SortBarDelegate: class { var sortDirection: SortDirection { get set } var sortMethod: SortMethod { get set } + var searchScope: SearchScope { get set } func sortBar(_ sortBar: SortBar, didUpdateSortMethod: SortMethod) + func sortBar(_ sortBar: SortBar, didUpdateSearchScope: SearchScope) + func sortBar(_ sortBar: SortBar, presentViewController: UIViewController, animated: Bool, completionHandler: (() -> Void)?) func toggleSelectMode() @@ -66,6 +85,7 @@ class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate { var sortSegmentedControl: SegmentedControl? var sortButton: UIButton? + var searchScopeSegmentedControl : SegmentedControl? var selectButton: UIButton? var showSelectButton: Bool = false { didSet { @@ -73,6 +93,19 @@ class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate { } } + var showSearchScope: Bool = false { + didSet { + self.searchScopeSegmentedControl?.isHidden = false + self.searchScopeSegmentedControl?.alpha = oldValue ? 1.0 : 0.0 + + UIView.animate(withDuration: 0.3, animations: { + self.searchScopeSegmentedControl?.alpha = self.showSearchScope ? 1.0 : 0.0 + }, completion: { (_) in + self.searchScopeSegmentedControl?.isHidden = !self.showSearchScope + }) + } + } + var sortMethod: SortMethod { didSet { if self.superview != nil { // Only toggle direction if the view is already in the view hierarchy (i.e. not during initial setup) @@ -103,36 +136,57 @@ class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate { } } + var searchScope : SearchScope { + didSet { + delegate?.searchScope = searchScope + searchScopeSegmentedControl?.selectedSegmentIndex = searchScope.rawValue + } + } + // MARK: - Init & Deinit - init(frame: CGRect, sortMethod: SortMethod) { + init(frame: CGRect, sortMethod: SortMethod, searchScope: SearchScope = .local) { sortSegmentedControl = SegmentedControl() selectButton = UIButton() sortButton = UIButton(type: .system) + searchScopeSegmentedControl = SegmentedControl() self.sortMethod = sortMethod + self.searchScope = searchScope super.init(frame: frame) - if let sortButton = sortButton, let sortSegmentedControl = sortSegmentedControl, let selectButton = selectButton { + if let sortButton = sortButton, let sortSegmentedControl = sortSegmentedControl, let searchScopeSegmentedControl = searchScopeSegmentedControl, let selectButton = selectButton { sortButton.translatesAutoresizingMaskIntoConstraints = false sortSegmentedControl.translatesAutoresizingMaskIntoConstraints = false selectButton.translatesAutoresizingMaskIntoConstraints = false + searchScopeSegmentedControl.translatesAutoresizingMaskIntoConstraints = false sortButton.accessibilityIdentifier = "sort-bar.sortButton" sortSegmentedControl.accessibilityIdentifier = "sort-bar.segmentedControl" + searchScopeSegmentedControl.accessibilityIdentifier = "sort-bar.searchScopeSegmentedControl" + + for scope in SearchScope.allCases { + searchScopeSegmentedControl.insertSegment(withTitle: scope.label, at: scope.rawValue, animated:false) + } + searchScopeSegmentedControl.selectedSegmentIndex = searchScope.rawValue + searchScopeSegmentedControl.isHidden = !self.showSearchScope + searchScopeSegmentedControl.addTarget(self, action: #selector(searchScopeValueChanged), for: .valueChanged) self.addSubview(sortSegmentedControl) self.addSubview(sortButton) + self.addSubview(searchScopeSegmentedControl) self.addSubview(selectButton) // Sort segmented control NSLayoutConstraint.activate([ sortSegmentedControl.topAnchor.constraint(equalTo: self.topAnchor, constant: topPadding), sortSegmentedControl.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -bottomPadding), - sortSegmentedControl.centerXAnchor.constraint(equalTo: self.centerXAnchor), - sortSegmentedControl.leftAnchor.constraint(greaterThanOrEqualTo: self.leftAnchor, constant: leftPadding), - sortSegmentedControl.rightAnchor.constraint(lessThanOrEqualTo: self.rightAnchor, constant: -rightPadding) + sortSegmentedControl.leftAnchor.constraint(equalTo: self.leftAnchor, constant: leftPadding), + + searchScopeSegmentedControl.trailingAnchor.constraint(equalTo: selectButton.leadingAnchor, constant: -10), + searchScopeSegmentedControl.topAnchor.constraint(equalTo: self.topAnchor, constant: topPadding), + searchScopeSegmentedControl.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -bottomPadding) ]) var longestTitleWidth : CGFloat = 0.0 @@ -207,6 +261,7 @@ class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate { self.sortButton?.applyThemeCollection(collection) self.selectButton?.applyThemeCollection(collection) self.sortSegmentedControl?.applyThemeCollection(collection) + self.searchScopeSegmentedControl?.applyThemeCollection(collection) self.backgroundColor = collection.navigationBarColors.backgroundColor } @@ -276,6 +331,13 @@ class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate { } } + @objc private func searchScopeValueChanged() { + if let selectedIndex = searchScopeSegmentedControl?.selectedSegmentIndex { + self.searchScope = SearchScope(rawValue: selectedIndex)! + delegate?.sortBar(self, didUpdateSearchScope: self.searchScope) + } + } + @objc private func toggleSelectMode() { delegate?.toggleSelectMode() } diff --git a/ownCloud/FileLists/QueryFileListTableViewController.swift b/ownCloud/FileLists/QueryFileListTableViewController.swift index 950f4ffd3..3af1557b4 100644 --- a/ownCloud/FileLists/QueryFileListTableViewController.swift +++ b/ownCloud/FileLists/QueryFileListTableViewController.swift @@ -81,14 +81,15 @@ class QueryFileListTableViewController: FileListTableViewController, SortBarDele return sort } } + var searchScope: SearchScope = .local var sortDirection: SortDirection { set { UserDefaults.standard.setValue(newValue.rawValue, forKey: "sort-direction") } get { - let sort = SortDirection(rawValue: UserDefaults.standard.integer(forKey: "sort-direction")) ?? SortDirection.ascendant - return sort + let direction = SortDirection(rawValue: UserDefaults.standard.integer(forKey: "sort-direction")) ?? SortDirection.ascendant + return direction } } @@ -99,18 +100,18 @@ class QueryFileListTableViewController: FileListTableViewController, SortBarDele func updateSearchResults(for searchController: UISearchController) { let searchText = searchController.searchBar.text! - let filterHandler: OCQueryFilterHandler = { (_, _, item) -> Bool in - if let itemName = item?.name { - return itemName.localizedCaseInsensitiveContains(searchText) - } - return false - } + applySearchFilter(for: (searchText == "") ? nil : searchText, to: query) + } - if searchText == "" { - if let filter = query.filter(withIdentifier: "text-search") { - query.removeFilter(filter) + func applySearchFilter(for searchText: String?, to query: OCQuery) { + if let searchText = searchText { + let filterHandler: OCQueryFilterHandler = { (_, _, item) -> Bool in + if let itemName = item?.name { + return itemName.localizedCaseInsensitiveContains(searchText) + } + return false } - } else { + if let filter = query.filter(withIdentifier: "text-search") { query.updateFilter(filter, applyChanges: { filterToChange in (filterToChange as? OCQueryFilter)?.filterHandler = filterHandler @@ -118,6 +119,10 @@ class QueryFileListTableViewController: FileListTableViewController, SortBarDele } else { query.addFilter(OCQueryFilter.init(handler: filterHandler), withIdentifier: "text-search") } + } else { + if let filter = query.filter(withIdentifier: "text-search") { + query.removeFilter(filter) + } } } @@ -200,6 +205,9 @@ class QueryFileListTableViewController: FileListTableViewController, SortBarDele self.present(presentViewController, animated: animated, completion: completionHandler) } + func sortBar(_ sortBar: SortBar, didUpdateSearchScope: SearchScope) { + } + func toggleSelectMode() { tableView.setEditing(!tableView.isEditing, animated: true) } @@ -211,45 +219,8 @@ class QueryFileListTableViewController: FileListTableViewController, SortBarDele func queryHasChangesAvailable(_ query: OCQuery) { queryRefreshRateLimiter.runRateLimitedBlock { - query.requestChangeSet(withFlags: OCQueryChangeSetRequestFlag(rawValue: 0)) { (query, changeSet) in + query.requestChangeSet(withFlags: .onlyResults) { (query, changeSet) in OnMainThread { - if query.state.isFinal { - OnMainThread { - if self.pullToRefreshControl?.isRefreshing == true { - self.pullToRefreshControl?.endRefreshing() - } - } - } - - let previousItemCount = self.items.count - - self.items = changeSet?.queryResult ?? [] - - switch query.state { - case .contentsFromCache, .idle, .waitingForServerReply: - if previousItemCount == 0, self.items.count == 0, query.state == .waitingForServerReply { - break - } - - if self.items.count == 0 { - if self.searchController?.searchBar.text != "" { - self.messageView?.message(show: true, imageName: "icon-search", title: "No matches".localized, message: "There is no results for this search".localized) - } else { - self.messageView?.message(show: true, imageName: "folder", title: "Empty folder".localized, message: "This folder contains no files or folders.".localized) - } - } else { - self.messageView?.message(show: false) - } - - self.tableView.reloadData() - case .targetRemoved: - self.messageView?.message(show: true, imageName: "folder", title: "Folder removed".localized, message: "This folder no longer exists on the server.".localized) - self.tableView.reloadData() - - default: - self.messageView?.message(show: false) - } - self.performUpdatesWithQueryChanges(query: query, changeSet: changeSet) } } @@ -257,6 +228,42 @@ class QueryFileListTableViewController: FileListTableViewController, SortBarDele } func performUpdatesWithQueryChanges(query: OCQuery, changeSet: OCQueryChangeSet?) { + if query.state.isFinal { + OnMainThread { + if self.pullToRefreshControl?.isRefreshing == true { + self.pullToRefreshControl?.endRefreshing() + } + } + } + + let previousItemCount = self.items.count + + self.items = changeSet?.queryResult ?? [] + + switch query.state { + case .contentsFromCache, .idle, .waitingForServerReply: + if previousItemCount == 0, self.items.count == 0, query.state == .waitingForServerReply { + break + } + + if self.items.count == 0 { + if self.searchController?.searchBar.text != "" { + self.messageView?.message(show: true, imageName: "icon-search", title: "No matches".localized, message: "There is no results for this search".localized) + } else { + self.messageView?.message(show: true, imageName: "folder", title: "Empty folder".localized, message: "This folder contains no files or folders.".localized) + } + } else { + self.messageView?.message(show: false) + } + + self.tableView.reloadData() + case .targetRemoved: + self.messageView?.message(show: true, imageName: "folder", title: "Folder removed".localized, message: "This folder no longer exists on the server.".localized) + self.tableView.reloadData() + + default: + self.messageView?.message(show: false) + } } // MARK: - Themeable @@ -287,6 +294,7 @@ class QueryFileListTableViewController: FileListTableViewController, SortBarDele sortBar = SortBar(frame: CGRect(x: 0, y: 0, width: self.tableView.frame.width, height: 40), sortMethod: sortMethod) sortBar?.delegate = self sortBar?.sortMethod = self.sortMethod + sortBar?.searchScope = self.searchScope sortBar?.updateForCurrentTraitCollection() tableView.tableHeaderView = sortBar diff --git a/ownCloud/UI Elements/MessageView.swift b/ownCloud/UI Elements/MessageView.swift index fd29e4416..89a931f94 100644 --- a/ownCloud/UI Elements/MessageView.swift +++ b/ownCloud/UI Elements/MessageView.swift @@ -173,10 +173,16 @@ class MessageView: UIView { self.composeViewBottomConstraint.constant = self.masterView.safeAreaInsets.bottom - keyboardHeight } + var masterTopAnchor = masterView.safeAreaLayoutGuide.topAnchor + + if let masterTableView = masterView as? UITableView, let masterTableHeaderView = masterTableView.tableHeaderView { + masterTopAnchor = masterTableHeaderView.safeAreaLayoutGuide.bottomAnchor + } + NSLayoutConstraint.activate([ rootView.leftAnchor.constraint(equalTo: self.masterView.leftAnchor), rootView.widthAnchor.constraint(equalTo: self.masterView.widthAnchor), - rootView.topAnchor.constraint(equalTo: self.masterView.safeAreaLayoutGuide.topAnchor), + rootView.topAnchor.constraint(equalTo: masterTopAnchor), self.composeViewBottomConstraint ])