Skip to content

Commit 8094dee

Browse files
authored
Merge pull request #5471 from woocommerce/issue/5404-bottom-sheet-to-create-order
Order Creation: Add bottom sheet to choose new order flow
2 parents 572107e + 4efc2e4 commit 8094dee

File tree

12 files changed

+375
-27
lines changed

12 files changed

+375
-27
lines changed

WooCommerce/Classes/Extensions/UIImage+Woo.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,12 @@ extension UIImage {
588588
.imageWithTintColor(tintColor)!
589589
}
590590

591+
/// Simple Payments Icon
592+
///
593+
static var simplePaymentsImage: UIImage {
594+
return UIImage(named: "icon-simple-payments")!
595+
}
596+
591597
/// Work In Progress banner icon on the Products Tab
592598
///
593599
static var workInProgressBanner: UIImage {

WooCommerce/Classes/ViewRelated/BottomSheet/ListSelector/BottomSheetListSelectorViewController.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ UIViewController, UITableViewDataSource, UITableViewDelegate where Command.Model
8181
}
8282

8383
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
84+
guard viewProperties.title != nil else {
85+
return nil
86+
}
8487
guard let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: BottomSheetListSelectorSectionHeaderView.reuseIdentifier)
8588
as? BottomSheetListSelectorSectionHeaderView else {
8689
fatalError()
@@ -103,7 +106,7 @@ private extension BottomSheetListSelectorViewController {
103106
tableView.dataSource = self
104107

105108
tableView.rowHeight = UITableView.automaticDimension
106-
tableView.estimatedSectionHeaderHeight = estimatedSectionHeight
109+
tableView.estimatedSectionHeaderHeight = viewProperties.title != nil ? estimatedSectionHeight : .zero
107110
tableView.sectionHeaderHeight = UITableView.automaticDimension
108111

109112
tableView.backgroundColor = .listForeground
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import UIKit
2+
import Yosemite
3+
4+
final class AddOrderCoordinator: Coordinator {
5+
var navigationController: UINavigationController
6+
7+
private let siteID: Int64
8+
private let isOrderCreationEnabled: Bool
9+
private let shouldShowSimplePaymentsButton: Bool
10+
private let sourceBarButtonItem: UIBarButtonItem?
11+
private let sourceView: UIView?
12+
13+
/// Assign this closure to be notified when a new order is created
14+
///
15+
var onOrderCreated: (Order) -> Void = { _ in }
16+
17+
init(siteID: Int64,
18+
isOrderCreationEnabled: Bool,
19+
shouldShowSimplePaymentsButton: Bool,
20+
sourceBarButtonItem: UIBarButtonItem,
21+
sourceNavigationController: UINavigationController) {
22+
self.siteID = siteID
23+
self.isOrderCreationEnabled = isOrderCreationEnabled
24+
self.shouldShowSimplePaymentsButton = shouldShowSimplePaymentsButton
25+
self.sourceBarButtonItem = sourceBarButtonItem
26+
self.sourceView = nil
27+
self.navigationController = sourceNavigationController
28+
}
29+
30+
required init?(coder aDecoder: NSCoder) {
31+
fatalError("init(coder:) has not been implemented")
32+
}
33+
34+
func start() {
35+
switch (isOrderCreationEnabled, shouldShowSimplePaymentsButton) {
36+
case (true, true):
37+
presentOrderTypeBottomSheet()
38+
case (false, true):
39+
presentOrderCreationFlow(bottomSheetOrderType: .simple)
40+
case (true, false):
41+
presentOrderCreationFlow(bottomSheetOrderType: .full)
42+
case (false, false):
43+
return
44+
}
45+
}
46+
}
47+
48+
// MARK: Navigation
49+
private extension AddOrderCoordinator {
50+
func presentOrderTypeBottomSheet() {
51+
let viewProperties = BottomSheetListSelectorViewProperties(title: nil)
52+
let command = OrderTypeBottomSheetListSelectorCommand() { selectedBottomSheetOrderType in
53+
self.navigationController.dismiss(animated: true)
54+
self.presentOrderCreationFlow(bottomSheetOrderType: selectedBottomSheetOrderType)
55+
}
56+
let productTypesListPresenter = BottomSheetListSelectorPresenter(viewProperties: viewProperties, command: command)
57+
productTypesListPresenter.show(from: navigationController, sourceView: sourceView, sourceBarButtonItem: sourceBarButtonItem, arrowDirections: .any)
58+
}
59+
60+
func presentOrderCreationFlow(bottomSheetOrderType: BottomSheetOrderType) {
61+
switch bottomSheetOrderType {
62+
case .simple:
63+
presentSimplePaymentsAmountController()
64+
case .full:
65+
presentNewOrderController()
66+
return
67+
}
68+
}
69+
70+
/// Presents `SimplePaymentsAmountHostingController`.
71+
///
72+
func presentSimplePaymentsAmountController() {
73+
let viewModel = SimplePaymentsAmountViewModel(siteID: siteID)
74+
viewModel.onOrderCreated = onOrderCreated
75+
76+
let viewController = SimplePaymentsAmountHostingController(viewModel: viewModel)
77+
let simplePaymentsNC = WooNavigationController(rootViewController: viewController)
78+
navigationController.present(simplePaymentsNC, animated: true)
79+
80+
ServiceLocator.analytics.track(event: WooAnalyticsEvent.SimplePayments.simplePaymentsFlowStarted())
81+
}
82+
83+
/// Presents `NewOrderHostingController`.
84+
///
85+
func presentNewOrderController() {
86+
let viewController = NewOrderHostingController()
87+
viewController.hidesBottomBarWhenPushed = true
88+
navigationController.pushViewController(viewController, animated: true)
89+
}
90+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import Yosemite
2+
3+
public enum BottomSheetOrderType: Hashable {
4+
case simple
5+
case full
6+
7+
/// Title shown on the action sheet.
8+
///
9+
var actionSheetTitle: String {
10+
switch self {
11+
case .simple:
12+
return NSLocalizedString("Simple Payments",
13+
comment: "Action sheet option when the user wants to create Simple Payments order")
14+
case .full:
15+
return NSLocalizedString("Create order",
16+
comment: "Action sheet option when the user wants to create full manual order")
17+
}
18+
}
19+
20+
/// Description shown on the action sheet.
21+
///
22+
var actionSheetDescription: String {
23+
switch self {
24+
case .simple:
25+
return NSLocalizedString("Create an order with minimal information",
26+
comment: "Description of the Action sheet option when the user wants to create Simple Payments order")
27+
case .full:
28+
return NSLocalizedString("Create a new manual order",
29+
comment: "Description of the Action sheet option when the user wants to create full manual order")
30+
}
31+
}
32+
33+
/// Image shown on the action sheet.
34+
///
35+
var actionSheetImage: UIImage {
36+
switch self {
37+
case .simple:
38+
return UIImage.simplePaymentsImage
39+
case .full:
40+
return UIImage.pagesImage
41+
}
42+
}
43+
}
44+
45+
final class OrderTypeBottomSheetListSelectorCommand: BottomSheetListSelectorCommand {
46+
typealias Model = BottomSheetOrderType
47+
typealias Cell = ImageAndTitleAndTextTableViewCell
48+
49+
var data: [BottomSheetOrderType] = [
50+
.full,
51+
.simple
52+
]
53+
54+
var selected: BottomSheetOrderType? = nil
55+
56+
private let onSelection: (BottomSheetOrderType) -> Void
57+
58+
init(onSelection: @escaping (BottomSheetOrderType) -> Void) {
59+
self.onSelection = onSelection
60+
}
61+
62+
func configureCell(cell: ImageAndTitleAndTextTableViewCell, model: BottomSheetOrderType) {
63+
let viewModel = ImageAndTitleAndTextTableViewCell.ViewModel(title: model.actionSheetTitle,
64+
text: model.actionSheetDescription,
65+
image: model.actionSheetImage,
66+
imageTintColor: .gray(.shade20),
67+
numberOfLinesForText: 0,
68+
isActionable: false)
69+
cell.updateUI(viewModel: viewModel)
70+
}
71+
72+
func handleSelectedChange(selected: BottomSheetOrderType) {
73+
onSelection(selected)
74+
}
75+
}

WooCommerce/Classes/ViewRelated/Orders/Order Creation/NewOrder.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
import SwiftUI
22

3+
/// Hosting controller that wraps an `NewOrder` view.
4+
///
5+
final class NewOrderHostingController: UIHostingController<NewOrder> {
6+
7+
init() {
8+
super.init(rootView: NewOrder())
9+
}
10+
11+
required dynamic init?(coder aDecoder: NSCoder) {
12+
fatalError("init(coder:) has not been implemented")
13+
}
14+
}
15+
316
/// View to create a new manual order
417
///
518
struct NewOrder: View {

WooCommerce/Classes/ViewRelated/Orders/OrdersRootViewController.swift

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ final class OrdersRootViewController: UIViewController {
6464

6565
override func viewWillAppear(_ animated: Bool) {
6666
// Needed in ViewWillAppear because this View Controller is never recreated.
67-
fetchSimplePaymentsExperimentalToggleAndConfigureNavigationButtons()
67+
fetchExperimentalTogglesAndConfigureNavigationButtons()
6868
}
6969

7070
override func viewDidLayoutSubviews() {
@@ -108,14 +108,15 @@ private extension OrdersRootViewController {
108108
/// Search: Is always present.
109109
/// Simple Payments: Depends on the local feature flag, experimental feature toggle and the inPersonPayments state.
110110
///
111-
func configureNavigationButtons(isSimplePaymentsExperimentalToggleEnabled: Bool) {
111+
func configureNavigationButtons(isSimplePaymentsExperimentalToggleEnabled: Bool, isOrderCreationExperimentalToggleEnabled: Bool) {
112112
let shouldShowSimplePaymentsButton: Bool = {
113113
let isInPersonPaymentsConfigured = inPersonPaymentsUseCase.state == .completed
114114
return isSimplePaymentsExperimentalToggleEnabled && isInPersonPaymentsConfigured
115115
}()
116116
let buttons: [UIBarButtonItem?] = [
117117
ordersViewController.createSearchBarButtonItem(),
118-
shouldShowSimplePaymentsButton ? ordersViewController.createAddSimplePaymentsOrderItem() : nil
118+
ordersViewController.createAddOrderItem(isOrderCreationEnabled: isOrderCreationExperimentalToggleEnabled,
119+
shouldShowSimplePaymentsButton: shouldShowSimplePaymentsButton)
119120
]
120121
navigationItem.rightBarButtonItems = buttons.compactMap { $0 }
121122
}
@@ -153,20 +154,38 @@ private extension OrdersRootViewController {
153154
inPersonPaymentsUseCase.$state
154155
.removeDuplicates()
155156
.sink { [weak self] _ in
156-
self?.fetchSimplePaymentsExperimentalToggleAndConfigureNavigationButtons()
157+
self?.fetchExperimentalTogglesAndConfigureNavigationButtons()
157158
}
158159
.store(in: &subscriptions)
159160
inPersonPaymentsUseCase.refresh()
160161
}
161162

162-
/// Fetches the latest value of the SimplePayments experimental feature toggle and re configures navigation buttons.
163+
/// Fetches the latest values of order-related experimental feature toggles and re configures navigation buttons.
163164
///
164-
func fetchSimplePaymentsExperimentalToggleAndConfigureNavigationButtons() {
165-
let action = AppSettingsAction.loadSimplePaymentsSwitchState { [weak self] result in
166-
let isEnabled = (try? result.get()) ?? false
167-
self?.configureNavigationButtons(isSimplePaymentsExperimentalToggleEnabled: isEnabled)
165+
func fetchExperimentalTogglesAndConfigureNavigationButtons() {
166+
let group = DispatchGroup()
167+
168+
var isSimplePaymentsEnabled = false
169+
var isOrderCreationEnabled = false
170+
171+
group.enter()
172+
let simplePaymentsAction = AppSettingsAction.loadSimplePaymentsSwitchState { result in
173+
isSimplePaymentsEnabled = (try? result.get()) ?? false
174+
group.leave()
175+
}
176+
ServiceLocator.stores.dispatch(simplePaymentsAction)
177+
178+
group.enter()
179+
let orderCreationAction = AppSettingsAction.loadOrderCreationSwitchState { result in
180+
isOrderCreationEnabled = (try? result.get()) ?? false
181+
group.leave()
182+
}
183+
ServiceLocator.stores.dispatch(orderCreationAction)
184+
185+
group.notify(queue: .main) { [weak self] in
186+
self?.configureNavigationButtons(isSimplePaymentsExperimentalToggleEnabled: isSimplePaymentsEnabled,
187+
isOrderCreationExperimentalToggleEnabled: isOrderCreationEnabled)
168188
}
169-
ServiceLocator.stores.dispatch(action)
170189
}
171190
}
172191

WooCommerce/Classes/ViewRelated/Orders/OrdersTabbedViewController.swift

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ final class OrdersTabbedViewController: ButtonBarPagerTabStripViewController {
5151

5252
private let siteID: Int64
5353

54+
private var isOrderCreationEnabled: Bool = false
55+
private var shouldShowSimplePaymentsButton: Bool = false
56+
5457
init(siteID: Int64) {
5558
self.siteID = siteID
5659
super.init(nibName: Self.nibName, bundle: nil)
@@ -130,24 +133,25 @@ final class OrdersTabbedViewController: ButtonBarPagerTabStripViewController {
130133
ServiceLocator.analytics.track(.orderOpen, withProperties: ["id": order.orderID, "status": order.status.rawValue])
131134
}
132135

133-
/// Presents `SimplePaymentsAmountHostingController`.
134-
///
135-
@objc private func presentSimplePaymentsAmountController() {
136-
let viewModel = SimplePaymentsAmountViewModel(siteID: siteID)
137-
viewModel.onOrderCreated = { [weak self] order in
136+
@objc func presentOrderCreationFlow(sender: UIBarButtonItem) {
137+
guard let navigationController = navigationController else {
138+
return
139+
}
140+
141+
let coordinatingController = AddOrderCoordinator(siteID: siteID,
142+
isOrderCreationEnabled: isOrderCreationEnabled,
143+
shouldShowSimplePaymentsButton: shouldShowSimplePaymentsButton,
144+
sourceBarButtonItem: sender,
145+
sourceNavigationController: navigationController)
146+
coordinatingController.onOrderCreated = { [weak self] order in
138147
guard let self = self else { return }
139148

140149
self.moveToViewController(at: 1, animated: false) // AllOrders list is at index 1
141150
self.dismiss(animated: true) {
142151
self.navigateToOrderDetail(order)
143152
}
144153
}
145-
146-
let viewController = SimplePaymentsAmountHostingController(viewModel: viewModel)
147-
let navigationController = WooNavigationController(rootViewController: viewController)
148-
present(navigationController, animated: true)
149-
150-
ServiceLocator.analytics.track(event: WooAnalyticsEvent.SimplePayments.simplePaymentsFlowStarted())
154+
coordinatingController.start()
151155
}
152156

153157
// MARK: - ButtonBarPagerTabStripViewController Conformance
@@ -262,16 +266,31 @@ extension OrdersTabbedViewController {
262266
return button
263267
}
264268

265-
/// Create a `UIBarButtonItem` to be used as a way to create a new simple payments order.
269+
/// Create a `UIBarButtonItem` to be used as a way to create a new order.
266270
///
267-
func createAddSimplePaymentsOrderItem() -> UIBarButtonItem {
271+
func createAddOrderItem(isOrderCreationEnabled: Bool, shouldShowSimplePaymentsButton: Bool) -> UIBarButtonItem? {
272+
self.isOrderCreationEnabled = isOrderCreationEnabled
273+
self.shouldShowSimplePaymentsButton = shouldShowSimplePaymentsButton
274+
268275
let button = UIBarButtonItem(image: .plusBarButtonItemImage,
269276
style: .plain,
270277
target: self,
271-
action: #selector(presentSimplePaymentsAmountController))
278+
action: #selector(presentOrderCreationFlow(sender:)))
272279
button.accessibilityTraits = .button
273-
button.accessibilityLabel = NSLocalizedString("Add simple payments order", comment: "Navigates to a screen to create a simple payments order")
274-
button.accessibilityIdentifier = "simple-payments-add-button"
280+
281+
switch (isOrderCreationEnabled, shouldShowSimplePaymentsButton) {
282+
case (false, false):
283+
return nil
284+
case (true, true):
285+
button.accessibilityLabel = NSLocalizedString("Choose new order type", comment: "Opens action sheet to choose a type of a new order")
286+
button.accessibilityIdentifier = "new-order-type-sheet-button"
287+
case (true, false):
288+
button.accessibilityLabel = NSLocalizedString("Add a new order", comment: "Navigates to a screen to create a full manual order")
289+
button.accessibilityIdentifier = "full-order-add-button"
290+
case (false, true):
291+
button.accessibilityLabel = NSLocalizedString("Add simple payments order", comment: "Navigates to a screen to create a simple payments order")
292+
button.accessibilityIdentifier = "simple-payments-add-button"
293+
}
275294
return button
276295
}
277296

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "icon-simple-payments.pdf",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
},
12+
"properties" : {
13+
"preserves-vector-representation" : true
14+
}
15+
}
Binary file not shown.

0 commit comments

Comments
 (0)