Skip to content

Commit 80bd8a6

Browse files
authored
Merge pull request #8617 from woocommerce/issue/8517-toolbar
Bulk Editing: Add bottom toolbar to the products list
2 parents fa55b3d + 3ea8bc2 commit 80bd8a6

File tree

5 files changed

+137
-13
lines changed

5 files changed

+137
-13
lines changed

WooCommerce/Classes/Extensions/UIButton+Helpers.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ extension UIButton {
101101

102102
setTitleColor(.accent, for: .normal)
103103
setTitleColor(.accentDark, for: .highlighted)
104+
setTitleColor(.buttonDisabledTitle, for: .disabled)
104105
}
105106

106107
/// Applies the Modal Cancel Button Style

WooCommerce/Classes/ViewRelated/Products/ProductsListViewModel.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ class ProductListViewModel {
1010
selectedProducts.count
1111
}
1212

13+
var bulkEditActionIsEnabled: Bool {
14+
!selectedProducts.isEmpty
15+
}
16+
1317
func productIsSelected(_ productToCheck: Product) -> Bool {
1418
return selectedProducts.contains(productToCheck)
1519
}

WooCommerce/Classes/ViewRelated/Products/ProductsViewController.swift

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,44 @@ final class ProductsViewController: UIViewController, GhostableViewController {
6666
///
6767
@IBOutlet private weak var toolbar: ToolbarView!
6868

69+
/// Top toolbar that shows the bulk edit CTA.
70+
///
71+
@IBOutlet private weak var bottomToolbar: ToolbarView! {
72+
didSet {
73+
bottomToolbar.isHidden = true
74+
bottomToolbar.backgroundColor = .systemColor(.secondarySystemGroupedBackground)
75+
bottomToolbar.setSubviews(leftViews: [], rightViews: [bulkEditButton])
76+
bottomToolbar.addDividerOnTop()
77+
}
78+
}
79+
80+
/// Bottom placeholder inside StackView to cover the safe area gap below the bottom toolbar.
81+
///
82+
@IBOutlet private weak var bottomPlaceholder: UIView! {
83+
didSet {
84+
bottomPlaceholder.backgroundColor = .systemColor(.secondarySystemGroupedBackground)
85+
}
86+
}
87+
6988
// Used to trick the navigation bar for large title (ref: issue 3 in p91TBi-45c-p2).
7089
private let hiddenScrollView = UIScrollView()
7190

7291
/// The filter CTA in the top toolbar.
7392
private lazy var filterButton: UIButton = UIButton(frame: .zero)
7493

94+
/// The bulk edit CTA in the bottom toolbar.
95+
private lazy var bulkEditButton: UIButton = {
96+
let button = UIButton(frame: .zero)
97+
button.setTitle(Localization.bulkEditingToolbarButtonTitle, for: .normal)
98+
button.addTarget(self, action: #selector(openBulkEditingOptions(sender:)), for: .touchUpInside)
99+
button.applyLinkButtonStyle()
100+
var configuration = UIButton.Configuration.plain()
101+
configuration.contentInsets = Constants.toolbarButtonInsets
102+
button.configuration = configuration
103+
button.isEnabled = false
104+
return button
105+
}()
106+
75107
/// Container of the top banner that shows that the Products feature is still work in progress.
76108
///
77109
private lazy var topBannerContainerView: SwappableSubviewContainerView = SwappableSubviewContainerView()
@@ -273,6 +305,7 @@ private extension ProductsViewController {
273305

274306
configureNavigationBarForEditing()
275307
showOrHideToolbar()
308+
showBottomToolbar()
276309
}
277310

278311
@objc func finishBulkEditing() {
@@ -284,6 +317,35 @@ private extension ProductsViewController {
284317

285318
configureNavigationBar()
286319
showOrHideToolbar()
320+
hideBottomToolbar()
321+
}
322+
323+
func updatedSelectedItems() {
324+
updateNavigationBarTitleForEditing()
325+
bulkEditButton.isEnabled = viewModel.bulkEditActionIsEnabled
326+
}
327+
328+
@objc func openBulkEditingOptions(sender: UIButton) {
329+
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
330+
331+
let updateStatus = UIAlertAction(title: Localization.bulkEditingStatusOption, style: .default) { _ in
332+
// TODO-8519: show UI for status update
333+
}
334+
let updatePrice = UIAlertAction(title: Localization.bulkEditingPriceOption, style: .default) { _ in
335+
// TODO-8520: show UI for price update
336+
}
337+
let cancelAction = UIAlertAction(title: Localization.cancel, style: .cancel)
338+
339+
actionSheet.addAction(updateStatus)
340+
actionSheet.addAction(updatePrice)
341+
actionSheet.addAction(cancelAction)
342+
343+
if let popoverController = actionSheet.popoverPresentationController {
344+
popoverController.sourceView = sender
345+
popoverController.sourceRect = sender.bounds
346+
}
347+
348+
present(actionSheet, animated: true)
287349
}
288350
}
289351

@@ -361,11 +423,11 @@ private extension ProductsViewController {
361423
}
362424

363425
func configureNavigationBarForEditing() {
364-
configureNavigationBarTitleForEditing()
426+
updateNavigationBarTitleForEditing()
365427
configureNavigationBarRightButtonItemsForEditing()
366428
}
367429

368-
func configureNavigationBarTitleForEditing() {
430+
func updateNavigationBarTitleForEditing() {
369431
let selectedProducts = viewModel.selectedProductsCount
370432
if selectedProducts == 0 {
371433
navigationItem.title = Localization.bulkEditingTitle
@@ -459,7 +521,9 @@ private extension ProductsViewController {
459521

460522
[sortButton, filterButton].forEach {
461523
$0.applyLinkButtonStyle()
462-
$0.contentEdgeInsets = Constants.toolbarButtonInsets
524+
var configuration = UIButton.Configuration.plain()
525+
configuration.contentInsets = Constants.toolbarButtonInsets
526+
$0.configuration = configuration
463527
}
464528

465529
toolbar.backgroundColor = .systemColor(.secondarySystemGroupedBackground)
@@ -491,6 +555,24 @@ private extension ProductsViewController {
491555

492556
toolbar.isHidden = filters.numberOfActiveFilters == 0 ? isEmpty : false
493557
}
558+
559+
func showBottomToolbar() {
560+
tabBarController?.tabBar.isHidden = true
561+
562+
// trigger safe area update
563+
if let tabBarController {
564+
let currentFrame = tabBarController.view.frame
565+
tabBarController.view.frame = currentFrame.insetBy(dx: 0, dy: 1)
566+
tabBarController.view.frame = currentFrame
567+
}
568+
569+
bottomToolbar.isHidden = false
570+
}
571+
572+
func hideBottomToolbar() {
573+
tabBarController?.tabBar.isHidden = false
574+
bottomToolbar.isHidden = true
575+
}
494576
}
495577

496578
// MARK: - Updates
@@ -673,7 +755,7 @@ extension ProductsViewController: UITableViewDelegate {
673755

674756
if tableView.isEditing {
675757
viewModel.selectProduct(product)
676-
configureNavigationBarTitleForEditing()
758+
updatedSelectedItems()
677759
} else {
678760
tableView.deselectRow(at: indexPath, animated: true)
679761

@@ -690,7 +772,7 @@ extension ProductsViewController: UITableViewDelegate {
690772

691773
let product = resultsController.object(at: indexPath)
692774
viewModel.deselectProduct(product)
693-
configureNavigationBarTitleForEditing()
775+
updatedSelectedItems()
694776
}
695777

696778
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
@@ -1109,7 +1191,7 @@ private extension ProductsViewController {
11091191
static let placeholderRowsPerSection = [3]
11101192
static let headerDefaultHeight = CGFloat(130)
11111193
static let headerContainerInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
1112-
static let toolbarButtonInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
1194+
static let toolbarButtonInsets = NSDirectionalEdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16)
11131195
}
11141196

11151197
enum Localization {
@@ -1120,6 +1202,14 @@ private extension ProductsViewController {
11201202
comment: "VoiceOver accessibility hint, informing the user the button can be used to bulk edit products"
11211203
)
11221204

1205+
static let bulkEditingToolbarButtonTitle = NSLocalizedString(
1206+
"Bulk update",
1207+
comment: "Title of a button that presents a menu with possible products bulk update options"
1208+
)
1209+
static let bulkEditingStatusOption = NSLocalizedString("Update status", comment: "Title of an option that opens bulk products status update flow")
1210+
static let bulkEditingPriceOption = NSLocalizedString("Update price", comment: "Title of an option that opens bulk products price update flow")
1211+
static let cancel = NSLocalizedString("Cancel", comment: "Title of an option to dismiss the bulk edit action sheet")
1212+
11231213
static let bulkEditingTitle = NSLocalizedString(
11241214
"Select items",
11251215
comment: "Title that appears on top of the Product List screen when bulk editing starts."

WooCommerce/Classes/ViewRelated/Products/ProductsViewController.xib

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
33
<device id="retina6_1" orientation="portrait" appearance="light"/>
44
<dependencies>
55
<deployment identifier="iOS"/>
6-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
6+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
77
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
88
<capability name="System colors in document resources" minToolsVersion="11.0"/>
99
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
1010
</dependencies>
1111
<objects>
1212
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ProductsViewController" customModule="WooCommerce" customModuleProvider="target">
1313
<connections>
14+
<outlet property="bottomPlaceholder" destination="xLN-As-fgq" id="bwi-vq-EvS"/>
15+
<outlet property="bottomToolbar" destination="pNN-uJ-nMs" id="Z6G-Im-KE6"/>
1416
<outlet property="tableView" destination="1mE-SE-uK9" id="ogX-nu-l5x"/>
1517
<outlet property="toolbar" destination="9eA-hc-k15" id="V2c-iT-WpM"/>
1618
<outlet property="view" destination="iN0-l3-epB" id="Jh9-wF-LZg"/>
@@ -22,21 +24,32 @@
2224
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
2325
<subviews>
2426
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="tDW-j0-dUT">
25-
<rect key="frame" x="0.0" y="44" width="414" height="818"/>
27+
<rect key="frame" x="0.0" y="48" width="414" height="848"/>
2628
<subviews>
2729
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9eA-hc-k15" userLabel="Filter Bar" customClass="ToolbarView" customModule="WooCommerce" customModuleProvider="target">
2830
<rect key="frame" x="0.0" y="0.0" width="414" height="50"/>
2931
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
3032
<constraints>
31-
<constraint firstAttribute="height" constant="50" id="sEi-OR-bvb"/>
33+
<constraint firstAttribute="height" priority="995" constant="50" id="sEi-OR-bvb"/>
3234
</constraints>
3335
</view>
3436
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="1mE-SE-uK9">
35-
<rect key="frame" x="0.0" y="50" width="414" height="768"/>
37+
<rect key="frame" x="0.0" y="50" width="414" height="714"/>
3638
<userDefinedRuntimeAttributes>
3739
<userDefinedRuntimeAttribute type="string" keyPath="accessibilityIdentifier" value="orders-table-view"/>
3840
</userDefinedRuntimeAttributes>
3941
</tableView>
42+
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pNN-uJ-nMs" userLabel="Bulk Edit Bar" customClass="ToolbarView" customModule="WooCommerce" customModuleProvider="target">
43+
<rect key="frame" x="0.0" y="764" width="414" height="50"/>
44+
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
45+
<constraints>
46+
<constraint firstAttribute="height" priority="995" constant="50" id="mXV-oU-3bM"/>
47+
</constraints>
48+
</view>
49+
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xLN-As-fgq" userLabel="Safe Area Placeholder">
50+
<rect key="frame" x="0.0" y="814" width="414" height="34"/>
51+
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
52+
</view>
4053
</subviews>
4154
<constraints>
4255
<constraint firstItem="9eA-hc-k15" firstAttribute="leading" secondItem="tDW-j0-dUT" secondAttribute="leading" id="2Md-DN-FrJ"/>
@@ -45,7 +58,7 @@
4558
<constraint firstItem="1mE-SE-uK9" firstAttribute="trailing" secondItem="9eA-hc-k15" secondAttribute="trailing" id="g0z-q4-rFD"/>
4659
<constraint firstItem="1mE-SE-uK9" firstAttribute="leading" secondItem="9eA-hc-k15" secondAttribute="leading" id="i2H-MX-SoN"/>
4760
<constraint firstItem="9eA-hc-k15" firstAttribute="top" secondItem="tDW-j0-dUT" secondAttribute="top" id="ilw-mJ-v1G"/>
48-
<constraint firstAttribute="bottom" secondItem="1mE-SE-uK9" secondAttribute="bottom" id="jCv-iK-SJb"/>
61+
<constraint firstItem="pNN-uJ-nMs" firstAttribute="top" secondItem="1mE-SE-uK9" secondAttribute="bottom" id="mlT-bM-H2X"/>
4962
</constraints>
5063
</stackView>
5164
</subviews>
@@ -54,8 +67,10 @@
5467
<constraints>
5568
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="tDW-j0-dUT" secondAttribute="trailing" id="3MC-6D-nnm"/>
5669
<constraint firstItem="tDW-j0-dUT" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="9UK-Qy-FXd"/>
57-
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="tDW-j0-dUT" secondAttribute="bottom" id="AQF-qF-3wt"/>
70+
<constraint firstAttribute="bottom" secondItem="tDW-j0-dUT" secondAttribute="bottom" id="AQF-qF-3wt"/>
71+
<constraint firstItem="xLN-As-fgq" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="bottom" id="Iu8-XK-w4W"/>
5872
<constraint firstItem="tDW-j0-dUT" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="KSv-iL-NFv"/>
73+
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="pNN-uJ-nMs" secondAttribute="bottom" id="xJE-yf-lID"/>
5974
</constraints>
6075
<point key="canvasLocation" x="100.00000000000001" y="48.883928571428569"/>
6176
</view>

WooCommerce/Classes/ViewRelated/Toolbar/ToolbarView.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ final class ToolbarView: UIView {
2626
pinSubviewToAllEdges(stackView)
2727
}
2828

29+
func addDividerOnTop() {
30+
let divider = UIView()
31+
divider.backgroundColor = .divider
32+
divider.translatesAutoresizingMaskIntoConstraints = false
33+
addSubview(divider)
34+
35+
NSLayoutConstraint.activate([
36+
divider.heightAnchor.constraint(equalToConstant: 1/UIScreen.main.scale),
37+
divider.topAnchor.constraint(equalTo: topAnchor),
38+
divider.leadingAnchor.constraint(equalTo: leadingAnchor),
39+
divider.trailingAnchor.constraint(equalTo: trailingAnchor)
40+
])
41+
}
42+
2943
func setSubviews(leftViews: [UIView], rightViews: [UIView]) {
3044
stackView.removeAllArrangedSubviews()
3145

0 commit comments

Comments
 (0)