Skip to content

Commit 2c3c255

Browse files
authored
Changed item sorting view to expand sort options when view is full screen (#1280)
1 parent f4fd107 commit 2c3c255

8 files changed

Lines changed: 171 additions & 282 deletions

File tree

Zotero.xcodeproj/project.pbxproj

Lines changed: 29 additions & 37 deletions
Large diffs are not rendered by default.

Zotero/Scenes/Detail/DetailCoordinator.swift

Lines changed: 28 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,17 @@ protocol DetailItemsCoordinatorDelegate: AnyObject {
3939
func showItemDetail(for type: ItemDetailState.DetailType, libraryId: LibraryIdentifier, scrolledToKey childKey: String?, animated: Bool)
4040
func showAttachmentError(_ error: Error)
4141
func showAddActions(viewModel: ViewModel<ItemsActionHandler>, button: UIBarButtonItem)
42-
func showSortActions(sortType: ItemsSortType, button: UIBarButtonItem, changed: @escaping (ItemsSortType) -> Void)
4342
func show(url: URL)
4443
func show(doi: String)
45-
func showFilters(filters: [ItemsFilter], filtersDelegate: BaseItemsViewController, button: UIBarButtonItem)
4644
func showDeletionQuestion(count: Int, confirmAction: @escaping () -> Void, cancelAction: @escaping () -> Void)
4745
func showRemoveFromCollectionQuestion(count: Int, confirmAction: @escaping () -> Void)
4846
func showCitation(using presenter: UIViewController?, for itemIds: Set<String>, libraryId: LibraryIdentifier, delegate: DetailCitationCoordinatorDelegate?)
4947
func copyBibliography(using presenter: UIViewController, for itemIds: Set<String>, libraryId: LibraryIdentifier, delegate: DetailCopyBibliographyCoordinatorDelegate?)
5048
func showCiteExport(for itemIds: Set<String>, libraryId: LibraryIdentifier)
5149
func showAttachment(key: String, parentKey: String?, libraryId: LibraryIdentifier, readerURL: URL?)
5250
func show(error: ItemsError)
51+
func showFilters(filters: [ItemsFilter], filtersDelegate: BaseItemsViewController, button: UIBarButtonItem)
52+
func dismissFilters()
5353
func showLookup()
5454
}
5555

@@ -481,66 +481,6 @@ extension DetailCoordinator: DetailItemsCoordinatorDelegate {
481481
self.navigationController?.present(controller, animated: true, completion: nil)
482482
}
483483

484-
func showSortActions(sortType: ItemsSortType, button: UIBarButtonItem, changed: @escaping (ItemsSortType) -> Void) {
485-
DDLogInfo("DetailCoordinator: show item sort popup")
486-
487-
let sortNavigationController = UINavigationController()
488-
sortNavigationController.modalPresentationStyle = .popover
489-
sortNavigationController.popoverPresentationController?.sourceItem = button
490-
let view = ItemSortingView(
491-
sortType: sortType,
492-
changed: changed,
493-
showPicker: { [weak sortNavigationController] view in
494-
guard let sortNavigationController else { return }
495-
showPicker(view: view, navigationController: sortNavigationController)
496-
},
497-
closePicker: { [weak sortNavigationController] in
498-
sortNavigationController?.popViewController(animated: true)
499-
}
500-
)
501-
let controller = createItemSortingController(for: view, navigationController: sortNavigationController)
502-
sortNavigationController.setViewControllers([controller], animated: false)
503-
navigationController?.present(sortNavigationController, animated: true, completion: nil)
504-
505-
func createItemSortingController(for view: ItemSortingView, navigationController: UINavigationController) -> UIHostingController<ItemSortingView> {
506-
let controller = DisappearActionHostingController(rootView: view)
507-
var size: CGSize?
508-
controller.willAppear = { [weak controller, weak navigationController] in
509-
guard let controller else { return }
510-
let _size = size ?? controller.view.systemLayoutSizeFitting(CGSize(width: 400.0, height: .greatestFiniteMagnitude))
511-
size = _size
512-
controller.preferredContentSize = _size
513-
navigationController?.preferredContentSize = _size
514-
}
515-
516-
if UIDevice.current.userInterfaceIdiom == .phone {
517-
controller.didLoad = { [weak self] viewController in
518-
guard let self else { return }
519-
let doneButton = UIBarButtonItem(title: L10n.done, style: .done, target: nil, action: nil)
520-
doneButton.rx.tap
521-
.subscribe({ [weak self] _ in
522-
self?.navigationController?.dismiss(animated: true)
523-
})
524-
.disposed(by: disposeBag)
525-
viewController.navigationItem.rightBarButtonItem = doneButton
526-
}
527-
}
528-
return controller
529-
}
530-
531-
func showPicker(view: ItemSortTypePickerView, navigationController: UINavigationController) {
532-
let controller = UIHostingController(rootView: view)
533-
controller.preferredContentSize = CGSize(width: 400, height: 600)
534-
navigationController.preferredContentSize = controller.preferredContentSize
535-
navigationController.pushViewController(controller, animated: true)
536-
}
537-
}
538-
539-
private func sortButtonTitles(for sortType: ItemsSortType) -> (field: String, order: String) {
540-
let sortOrderTitle = sortType.ascending ? L10n.Items.ascending : L10n.Items.descending
541-
return ("\(L10n.Items.sortBy): \(sortType.field.title)", "\(L10n.Items.sortOrder): \(sortOrderTitle)")
542-
}
543-
544484
func createNoteController(
545485
library: Library,
546486
kind: NoteEditorKind,
@@ -655,27 +595,6 @@ extension DetailCoordinator: DetailItemsCoordinatorDelegate {
655595
self.navigationController?.present(navigationController, animated: true, completion: nil)
656596
}
657597

658-
func showFilters(filters: [ItemsFilter], filtersDelegate: BaseItemsViewController, button: UIBarButtonItem) {
659-
DDLogInfo("DetailCoordinator: show item filters")
660-
661-
let navigationController = NavigationViewController()
662-
navigationController.modalPresentationStyle = .popover
663-
navigationController.popoverPresentationController?.sourceItem = button
664-
665-
let coordinator = ItemsFilterCoordinator(
666-
filters: filters,
667-
filtersDelegate: filtersDelegate,
668-
navigationController: navigationController,
669-
mainCoordinatorDelegate: mainCoordinatorDelegate,
670-
controllers: controllers
671-
)
672-
coordinator.parentCoordinator = self
673-
childCoordinators.append(coordinator)
674-
coordinator.start(animated: false)
675-
676-
self.navigationController?.present(navigationController, animated: true, completion: nil)
677-
}
678-
679598
func showDeletionQuestion(count: Int, confirmAction: @escaping () -> Void, cancelAction: @escaping () -> Void) {
680599
let question = L10n.Items.deleteQuestion(count)
681600
self.ask(question: question, title: L10n.delete, isDestructive: true, confirm: confirmAction, cancel: cancelAction)
@@ -754,6 +673,32 @@ extension DetailCoordinator: DetailItemsCoordinatorDelegate {
754673
self.navigationController?.present(containerController, animated: true, completion: nil)
755674
}
756675

676+
func showFilters(filters: [ItemsFilter], filtersDelegate: BaseItemsViewController, button: UIBarButtonItem) {
677+
DDLogInfo("DetailCoordinator: show item filters")
678+
679+
let navigationController = NavigationViewController()
680+
navigationController.modalPresentationStyle = .popover
681+
navigationController.popoverPresentationController?.sourceItem = button
682+
683+
let coordinator = ItemsFilterCoordinator(
684+
filters: filters,
685+
filtersDelegate: filtersDelegate,
686+
navigationController: navigationController,
687+
mainCoordinatorDelegate: mainCoordinatorDelegate,
688+
controllers: controllers
689+
)
690+
coordinator.parentCoordinator = self
691+
childCoordinators.append(coordinator)
692+
coordinator.start(animated: false)
693+
694+
self.navigationController?.present(navigationController, animated: true, completion: nil)
695+
}
696+
697+
func dismissFilters() {
698+
guard let coordinator = childCoordinators.first(where: { $0 is ItemsFilterCoordinator }) as? ItemsFilterCoordinator else { return }
699+
coordinator.navigationController?.dismiss(animated: true)
700+
}
701+
757702
func show(error: ItemsError) {
758703
let message: String
759704
switch error {

Zotero/Scenes/Detail/Items/ViewModels/ItemsToolbarController.swift

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@ import RxSwift
1414
protocol ItemsToolbarControllerDelegate: UITraitEnvironment {
1515
func process(action: ItemAction.Kind, button: UIBarButtonItem)
1616
func showLookup()
17+
func showFilters(button: UIBarButtonItem)
18+
func sortTypeChanged(_ sortType: ItemsSortType)
19+
func downloadsFilterChanged(enabled: Bool)
1720
}
1821

1922
final class ItemsToolbarController {
2023
struct Data {
2124
let isEditing: Bool
2225
let selectedItems: Set<AnyHashable>
2326
let filters: [ItemsFilter]
27+
let sortType: ItemsSortType
2428
let allowsManualSort: Bool
2529
let downloadBatchData: ItemsState.DownloadBatchData?
2630
let remoteDownloadBatchData: ItemsState.DownloadBatchData?
@@ -33,7 +37,8 @@ final class ItemsToolbarController {
3337
case single
3438
case filter
3539
case title
36-
40+
case sort
41+
3742
var tag: Int {
3843
rawValue
3944
}
@@ -76,6 +81,10 @@ final class ItemsToolbarController {
7681
}
7782
}
7883

84+
private var isCompact: Bool {
85+
delegate?.traitCollection.horizontalSizeClass == .compact || UIDevice.current.userInterfaceIdiom == .phone
86+
}
87+
7988
func willAppear() {
8089
viewController.navigationController?.setToolbarHidden(false, animated: false)
8190
}
@@ -91,6 +100,7 @@ final class ItemsToolbarController {
91100
viewController.toolbarItems = createNormalToolbarItems(for: filters)
92101
updateNormalToolbarItems(
93102
for: filters,
103+
sortType: data.sortType,
94104
downloadBatchData: data.downloadBatchData,
95105
remoteDownloadBatchData: data.remoteDownloadBatchData,
96106
identifierLookupBatchData: data.identifierLookupBatchData,
@@ -161,12 +171,17 @@ final class ItemsToolbarController {
161171

162172
let filterImageName = filters.isEmpty ? "line.horizontal.3.decrease.circle" : "line.horizontal.3.decrease.circle.fill"
163173
let filterButton = UIBarButtonItem(image: UIImage(systemName: filterImageName), style: .plain, target: nil, action: nil)
174+
if isCompact {
175+
filterButton.primaryAction = UIAction { [weak self, weak filterButton] _ in
176+
guard let filterButton else { return }
177+
self?.delegate?.showFilters(button: filterButton)
178+
}
179+
} else {
180+
let downloadsFilterEnabled = data.filters.contains(where: { $0.isDownloadedFilesFilter })
181+
filterButton.menu = createFilterMenu(downloadsFilterEnabled: downloadsFilterEnabled)
182+
}
164183
filterButton.tag = ToolbarItem.filter.tag
165184
filterButton.accessibilityLabel = L10n.Accessibility.Items.filterItems
166-
filterButton.rx.tap.subscribe(onNext: { [weak self] _ in
167-
self?.delegate?.process(action: .filter, button: filterButton)
168-
})
169-
.disposed(by: disposeBag)
170185

171186
let titleButton = UIBarButtonItem(customView: createTitleView())
172187
titleButton.tag = ToolbarItem.title.tag
@@ -175,13 +190,9 @@ final class ItemsToolbarController {
175190

176191
if data.allowsManualSort {
177192
let action = ItemAction(type: .sort)
178-
let sortButton = UIBarButtonItem(image: action.image, style: .plain, target: nil, action: nil)
193+
let sortButton = UIBarButtonItem(image: action.image, menu: createSortMenu(for: data.sortType))
194+
sortButton.tag = ToolbarItem.sort.tag
179195
sortButton.accessibilityLabel = L10n.Accessibility.Items.sortItems
180-
sortButton.rx.tap.subscribe(onNext: { [weak self] _ in
181-
self?.delegate?.process(action: action.type, button: sortButton)
182-
})
183-
.disposed(by: disposeBag)
184-
185196
items.append(contentsOf: [flexibleSpacer, sortButton, fixedSpacer])
186197
} else {
187198
items.append(contentsOf: [flexibleSpacer, fixedSpacer])
@@ -224,6 +235,7 @@ final class ItemsToolbarController {
224235
} else {
225236
updateNormalToolbarItems(
226237
for: sizeClassSpecificFilters(from: data.filters),
238+
sortType: data.sortType,
227239
downloadBatchData: data.downloadBatchData,
228240
remoteDownloadBatchData: data.remoteDownloadBatchData,
229241
identifierLookupBatchData: data.identifierLookupBatchData,
@@ -250,6 +262,37 @@ final class ItemsToolbarController {
250262
})
251263
}
252264

265+
private func createFilterMenu(downloadsFilterEnabled: Bool) -> UIMenu {
266+
let downloadsAction = UIAction(title: L10n.Items.Filters.downloads, image: UIImage(systemName: "arrow.down.circle"), state: downloadsFilterEnabled ? .on : .off) { [weak self] _ in
267+
self?.delegate?.downloadsFilterChanged(enabled: !downloadsFilterEnabled)
268+
}
269+
return UIMenu(title: L10n.Items.Filters.title, children: [downloadsAction])
270+
}
271+
272+
private func createSortMenu(for sortType: ItemsSortType) -> UIMenu {
273+
let ascendingAction = UIAction(title: L10n.Items.ascending, state: sortType.ascending ? .on : .off) { [weak self] _ in
274+
var newSortType = sortType
275+
newSortType.ascending = true
276+
self?.delegate?.sortTypeChanged(newSortType)
277+
}
278+
let descendingAction = UIAction(title: L10n.Items.descending, state: sortType.ascending ? .off : .on) { [weak self] _ in
279+
var newSortType = sortType
280+
newSortType.ascending = false
281+
self?.delegate?.sortTypeChanged(newSortType)
282+
}
283+
let orderMenu = UIMenu(title: L10n.Items.sortOrder, options: .displayInline, children: [ascendingAction, descendingAction])
284+
285+
let fieldActions = ItemsSortType.Field.allCases.map { field in
286+
UIAction(title: field.title, state: sortType.field == field ? .on : .off) { [weak self] _ in
287+
let newSortType = ItemsSortType(field: field, ascending: field.defaultOrderAscending)
288+
self?.delegate?.sortTypeChanged(newSortType)
289+
}
290+
}
291+
let fieldsMenu = UIMenu(title: L10n.Items.sortBy, options: .displayInline, children: fieldActions)
292+
293+
return UIMenu(children: [orderMenu, fieldsMenu])
294+
}
295+
253296
// MARK: - Helpers
254297

255298
private func updateEditingToolbarItems(for selectedItems: Set<AnyHashable>) {
@@ -269,14 +312,30 @@ final class ItemsToolbarController {
269312

270313
private func updateNormalToolbarItems(
271314
for filters: [ItemsFilter],
315+
sortType: ItemsSortType,
272316
downloadBatchData: ItemsState.DownloadBatchData?,
273317
remoteDownloadBatchData: ItemsState.DownloadBatchData?,
274318
identifierLookupBatchData: ItemsState.IdentifierLookupBatchData,
275319
itemCount: Int
276320
) {
321+
if let item = viewController.toolbarItems?.first(where: { $0.tag == ToolbarItem.sort.tag }) {
322+
item.menu = createSortMenu(for: sortType)
323+
}
324+
277325
if let item = viewController.toolbarItems?.first(where: { $0.tag == ToolbarItem.filter.tag }) {
278326
let filterImageName = filters.isEmpty ? "line.horizontal.3.decrease.circle" : "line.horizontal.3.decrease.circle.fill"
279327
item.image = UIImage(systemName: filterImageName)
328+
if isCompact {
329+
item.menu = nil
330+
item.primaryAction = UIAction { [weak self, weak item] _ in
331+
guard let item else { return }
332+
self?.delegate?.showFilters(button: item)
333+
}
334+
} else {
335+
item.primaryAction = nil
336+
let downloadsFilterEnabled = filters.contains(where: { $0.isDownloadedFilesFilter })
337+
item.menu = createFilterMenu(downloadsFilterEnabled: downloadsFilterEnabled)
338+
}
280339
}
281340

282341
if let item = viewController.toolbarItems?.first(where: { $0.tag == ToolbarItem.title.tag }),

Zotero/Scenes/Detail/Items/Views/BaseItemsViewController.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ class BaseItemsViewController: UIViewController {
102102

103103
// willTransition(to:with:) seems to not be not called for all transitions, so instead traitCollectionDidChange(_:) is used w/ a short animation block.
104104
guard UIDevice.current.userInterfaceIdiom == .pad, traitCollection.horizontalSizeClass != previousTraitCollection?.horizontalSizeClass else { return }
105+
if traitCollection.horizontalSizeClass == .regular {
106+
coordinatorDelegate?.dismissFilters()
107+
}
105108
setupTitle()
106109
UIView.animate(withDuration: 0.1) {
107110
self.toolbarController?.reloadToolbarItems(for: self.toolbarData)
@@ -287,6 +290,7 @@ class BaseItemsViewController: UIViewController {
287290
isEditing: false,
288291
selectedItems: [],
289292
filters: [],
293+
sortType: .default,
290294
allowsManualSort: true,
291295
downloadBatchData: nil,
292296
remoteDownloadBatchData: nil,

Zotero/Scenes/Detail/Items/Views/ItemSortTypePickerView.swift

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)