@@ -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. "
0 commit comments