Skip to content

Commit b4a1165

Browse files
authored
Merge branch 'trunk' into issue/6950-orders-split-view
2 parents d9cc204 + cae87f6 commit b4a1165

File tree

18 files changed

+317
-99
lines changed

18 files changed

+317
-99
lines changed

RELEASE-NOTES.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
- [*] Orders: Now it's possible to select and copy text from the notes on an order. [https://github.com/woocommerce/woocommerce-ios/pull/6894]
77
- [*] Support Arabic numerals on amount fields. [https://github.com/woocommerce/woocommerce-ios/pull/6891]
88
- [*] Product Selector: Enabled selecting all variations on variable product rows. [https://github.com/woocommerce/woocommerce-ios/pull/6899]
9+
- [*] Coupons: Now it's possible to update discount types for coupons. [https://github.com/woocommerce/woocommerce-ios/pull/6935]
910
- [*] Orders tab: the view width now adjusts to the app in tablet split view on iOS 15. [https://github.com/woocommerce/woocommerce-ios/pull/6951]
10-
11+
1112
9.2
1213
-----
1314
- [***] Experimental Features: Coupons editing and deletion features are now enabled as part of coupon management. [https://github.com/woocommerce/woocommerce-ios/pull/6853]

WooCommerce/Classes/Extensions/UIImage+Woo.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,24 @@ extension UIImage {
293293
return UIImage.gridicon(.filter)
294294
}
295295

296+
/// Fixed cart discount icon
297+
///
298+
static var fixedCartDiscountIcon: UIImage {
299+
return UIImage(named: "icon-fixed-cart-discount")!
300+
}
301+
302+
/// Fixed product discount icon
303+
///
304+
static var fixedProductDiscountIcon: UIImage {
305+
return UIImage(named: "icon-fixed-product-discount")!
306+
}
307+
308+
/// Percentage discount icon
309+
///
310+
static var percentageDiscountIcon: UIImage {
311+
return UIImage(named: "icon-percentage-discount")!
312+
}
313+
296314
/// Gift Icon (with a red dot at the top right corner)
297315
///
298316
static var giftWithTopRightRedDotImage: UIImage {

WooCommerce/Classes/Model/Coupon+Woo.swift

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,33 +18,33 @@ extension Coupon.DiscountType {
1818
}
1919
}
2020

21-
/// Localized title to be displayed for the discount type in `AddEditCoupon` when in creation mode.
21+
/// Description to be displayed on the discount type list selector.
2222
///
23-
var titleCreateCoupon: String {
23+
var actionSheetDescription: String? {
2424
switch self {
2525
case .percent:
26-
return Localization.titleCreatePercentageDiscount
26+
return Localization.percentageDiscountDescription
2727
case .fixedCart:
28-
return Localization.titleCreateFixedCartDiscount
28+
return Localization.fixedCartDiscountDescription
2929
case .fixedProduct:
30-
return Localization.titleCreateFixedProductDiscount
31-
default:
32-
return Localization.titleCreateGenericDiscount
30+
return Localization.fixedProductDiscountDescription
31+
case .other:
32+
return nil
3333
}
3434
}
3535

36-
/// Localized title to be displayed for the discount type in `AddEditCoupon` when in editing mode.
36+
/// Image to be displayed on the discount type list selector
3737
///
38-
var titleEditCoupon: String {
38+
var actionSheetIcon: UIImage? {
3939
switch self {
4040
case .percent:
41-
return Localization.titleEditPercentageDiscount
41+
return UIImage.percentageDiscountIcon
4242
case .fixedCart:
43-
return Localization.titleEditFixedCartDiscount
43+
return UIImage.fixedCartDiscountIcon
4444
case .fixedProduct:
45-
return Localization.titleEditFixedProductDiscount
46-
default:
47-
return Localization.titleEditGenericDiscount
45+
return UIImage.fixedProductDiscountIcon
46+
case .other:
47+
return nil
4848
}
4949
}
5050

@@ -53,30 +53,18 @@ extension Coupon.DiscountType {
5353
static let fixedCartDiscount = NSLocalizedString("Fixed Cart Discount", comment: "Name of fixed cart discount type")
5454
static let fixedProductDiscount = NSLocalizedString("Fixed Product Discount", comment: "Name of fixed product discount type")
5555
static let otherDiscount = NSLocalizedString("Other", comment: "Generic name of non-default discount types")
56-
static let titleEditPercentageDiscount = NSLocalizedString(
57-
"Edit percentage discount",
58-
comment: "Title of the view for editing a coupon with percentage discount.")
59-
static let titleEditFixedCartDiscount = NSLocalizedString(
60-
"Edit fixed cart discount",
61-
comment: "Title of the view for editing a coupon with fixed cart discount.")
62-
static let titleEditFixedProductDiscount = NSLocalizedString(
63-
"Edit fixed product discount",
64-
comment: "Title of the view for editing a coupon with fixed product discount.")
65-
static let titleEditGenericDiscount = NSLocalizedString(
66-
"Edit discount",
67-
comment: "Title of the view for editing a coupon with generic discount.")
68-
static let titleCreatePercentageDiscount = NSLocalizedString(
69-
"Create percentage discount",
70-
comment: "Title of the view for creating a coupon with percentage discount.")
71-
static let titleCreateFixedCartDiscount = NSLocalizedString(
72-
"Create fixed cart discount",
73-
comment: "Title of the view for creating a coupon with fixed cart discount.")
74-
static let titleCreateFixedProductDiscount = NSLocalizedString(
75-
"Create fixed product discount",
76-
comment: "Title of the view for creating a coupon with fixed product discount.")
77-
static let titleCreateGenericDiscount = NSLocalizedString(
78-
"Create discount",
79-
comment: "Title of the view for creating a coupon with generic discount.")
56+
static let percentageDiscountDescription = NSLocalizedString(
57+
"Create a percentage discount for selected products",
58+
comment: "Description for percentage discount type on the action sheet presented from Add or Edit coupon screen"
59+
)
60+
static let fixedCartDiscountDescription = NSLocalizedString(
61+
"Create a fixed total discount for the entire cart",
62+
comment: "Description for fixed cart discount type on the action sheet presented from Add or Edit coupon screen"
63+
)
64+
static let fixedProductDiscountDescription = NSLocalizedString(
65+
"Create a fixed total discount for selected products",
66+
comment: "Description for fixed product discount type on the action sheet presented from Add or Edit coupon screen"
67+
)
8068
}
8169
}
8270

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import SwiftUI
2+
3+
/// SwiftUI wrapper of `BottomSheetListSelectorViewController`.
4+
///
5+
struct BottomSheetListSelector<Command: BottomSheetListSelectorCommand, Model, Cell>: UIViewControllerRepresentable
6+
where Command.Model == Model, Command.Cell == Cell {
7+
8+
private let viewProperties: BottomSheetListSelectorViewProperties
9+
private let command: Command
10+
private let onDismiss: ((_ selected: Model?) -> Void)?
11+
12+
init(viewProperties: BottomSheetListSelectorViewProperties,
13+
command: Command,
14+
onDismiss: ((_ selected: Model?) -> Void)?) {
15+
self.viewProperties = viewProperties
16+
self.command = command
17+
self.onDismiss = onDismiss
18+
}
19+
20+
func makeUIViewController(context: Context) -> BottomSheetListSelectorViewController<Command, Model, Cell> {
21+
return BottomSheetListSelectorViewController(viewProperties: viewProperties, command: command, onDismiss: onDismiss)
22+
}
23+
24+
func updateUIViewController(_ uiViewController: BottomSheetListSelectorViewController<Command, Model, Cell>, context: Context) {
25+
// no-op
26+
}
27+
}

WooCommerce/Classes/ViewRelated/Coupons/Add and Edit Coupons/AddEditCoupon.swift

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ final class AddEditCouponHostingController: UIHostingController<AddEditCoupon> {
1313
rootView.dismissHandler = { [weak self] in
1414
self?.dismiss(animated: true)
1515
}
16+
17+
rootView.discountTypeHandler = { [weak self] viewProperties in
18+
guard let self = self else { return }
19+
let command = DiscountTypeBottomSheetListSelectorCommand(selected: self.viewModel.discountType) { [weak self] selectedType in
20+
guard let self = self else { return }
21+
viewModel.discountType = selectedType
22+
self.presentedViewController?.dismiss(animated: true, completion: nil)
23+
}
24+
let presenter = BottomSheetListSelectorPresenter(viewProperties: viewProperties, command: command)
25+
presenter.show(from: self, sourceView: self.view, sourceBarButtonItem: nil, arrowDirections: .any)
26+
}
1627
}
1728

1829
required dynamic init?(coder aDecoder: NSCoder) {
@@ -49,13 +60,18 @@ struct AddEditCoupon: View {
4960
///
5061
var onDisappear: () -> Void = {}
5162

63+
/// Set this closure to display the bottom sheet for discount type selection the UIKit way.
64+
///
65+
var discountTypeHandler: (BottomSheetListSelectorViewProperties) -> Void = { _ in }
66+
5267
@ObservedObject private var viewModel: AddEditCouponViewModel
5368
@State private var showingEditDescription: Bool = false
5469
@State private var showingCouponExpiryActionSheet: Bool = false
5570
@State private var showingCouponExpiryDate: Bool = false
5671
@State private var showingCouponRestrictions: Bool = false
5772
@State private var showingSelectProducts: Bool = false
5873
@State private var showingSelectCategories: Bool = false
74+
@State private var showingDiscountType: Bool = false
5975

6076
private var expiryDateActionSheetButtons: [Alert.Button] {
6177
var buttons: [Alert.Button] = []
@@ -83,6 +99,9 @@ struct AddEditCoupon: View {
8399
return buttons
84100
}
85101

102+
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
103+
private let viewProperties = BottomSheetListSelectorViewProperties(title: Localization.discountTypeSheetTitle)
104+
86105
private let categorySelectorConfig = ProductCategorySelector.Configuration.categoriesForCoupons
87106
private let categoryListConfig = ProductCategoryListViewController.Configuration(searchEnabled: true, clearSelectionEnabled: true)
88107

@@ -99,6 +118,26 @@ struct AddEditCoupon: View {
99118
ListHeaderView(text: Localization.headerCouponDetails.uppercased(), alignment: .left)
100119

101120
Group {
121+
TitleAndValueRow(title: Localization.discountType,
122+
value: viewModel.discountTypeValue,
123+
selectionStyle: .disclosure, action: {
124+
// TODO: remove this workaround with `adaptiveSheetPresentationController` when we drop support for iOS 14
125+
if idiom == .pad {
126+
showingDiscountType.toggle()
127+
} else {
128+
discountTypeHandler(viewProperties)
129+
}
130+
}).popover(isPresented: $showingDiscountType) {
131+
let command = DiscountTypeBottomSheetListSelectorCommand(selected: viewModel.discountType) { selectedType in
132+
viewModel.discountType = selectedType
133+
showingDiscountType.toggle()
134+
}
135+
BottomSheetListSelector(viewProperties: viewProperties, command: command, onDismiss: nil)
136+
}
137+
138+
Divider()
139+
.padding(.leading, Constants.margin)
140+
102141
TitleAndTextFieldRow(title: viewModel.amountLabel,
103142
placeholder: "0",
104143
text: $viewModel.amountField,
@@ -336,7 +375,7 @@ private extension AddEditCoupon {
336375
comment: "Header of the coupon details in the view for adding or editing a coupon.")
337376
static let couponCode = NSLocalizedString(
338377
"Coupon Code",
339-
comment: "Text field coupon code in the view for adding or editing a coupon.")
378+
comment: "Text field coupon code in the view for adding or editing a coupon code.")
340379
static let couponCodePlaceholder = NSLocalizedString(
341380
"Enter a coupon",
342381
comment: "Text field coupon code placeholder in the view for adding or editing a coupon.")
@@ -345,16 +384,20 @@ private extension AddEditCoupon {
345384
comment: "The footer of the text field coupon code in the view for adding or editing a coupon.")
346385
static let regenerateCouponCodeButton = NSLocalizedString(
347386
"Regenerate Coupon Code",
348-
comment: "Button in the view for adding or editing a coupon.")
387+
comment: "Button in the view for adding or editing a coupon code.")
349388
static let couponExpiryDate = NSLocalizedString(
350389
"Coupon Expiry Date",
351-
comment: "Field in the view for adding or editing a coupon.")
390+
comment: "Field in the view for adding or editing a coupon's expiry date.")
391+
static let discountType = NSLocalizedString(
392+
"Discount Type",
393+
comment: "Field in the view for adding or editing a coupon's discount type.")
352394
static let includeFreeShipping = NSLocalizedString(
353395
"Include Free Shipping?",
354-
comment: "Toggle field in the view for adding or editing a coupon.")
396+
comment: "Toggle field in the view for adding or editing a coupon's free shipping support.")
355397
static let headerApplyCouponTo = NSLocalizedString(
356398
"Apply this coupon to",
357-
comment: "Header of the section for applying a coupon to specific products or categories in the view for adding or editing a coupon.")
399+
comment: "Header of the section for applying a coupon to specific products or categories in the view " +
400+
"for adding or editing a coupon's product and category restrictions.")
358401
static let allProductsButton = NSLocalizedString(
359402
"All Products",
360403
comment: "Button indicating that coupon can be applied to all products in the view for adding or editing a coupon.")
@@ -381,6 +424,10 @@ private extension AddEditCoupon {
381424
comment: "Button in the action sheet for adding the expiration date for a coupon.")
382425
static let titleEditDescriptionView = NSLocalizedString("Coupon Description",
383426
comment: "Title of the view for editing the coupon description.")
427+
static let discountTypeSheetTitle = NSLocalizedString(
428+
"Discount Type",
429+
comment: "Title for the sheet to select discount type on the Add or Edit coupon screen."
430+
)
384431
}
385432
}
386433

WooCommerce/Classes/ViewRelated/Coupons/Add and Edit Coupons/AddEditCouponViewModel.swift

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,18 @@ final class AddEditCouponViewModel: ObservableObject {
2323
var title: String {
2424
switch editingOption {
2525
case .creation:
26-
return discountType.titleCreateCoupon
26+
return Localization.createCouponTitle
2727
case .editing:
28-
return discountType.titleEditCoupon
28+
return Localization.editCouponTitle
2929
}
3030
}
3131

32+
/// The value for populating the coupon discount type field based on the `discountType`.
33+
///
34+
var discountTypeValue: TitleAndValueRow.Value {
35+
return .content(discountType.localizedName)
36+
}
37+
3238
/// Label representing the label of the amount field, localized based on discount type.
3339
///
3440
var amountLabel: String {
@@ -123,7 +129,8 @@ final class AddEditCouponViewModel: ObservableObject {
123129

124130
var hasChangesMade: Bool {
125131
let coupon = populatedCoupon
126-
return checkAmountUpdated(for: coupon) ||
132+
return checkDiscountTypeUpdated(for: coupon) ||
133+
checkAmountUpdated(for: coupon) ||
127134
checkDescriptionUpdated(for: coupon) ||
128135
checkCouponCodeUpdated(for: coupon) ||
129136
checkAllowedProductsAndCategoriesUpdated(for: coupon) ||
@@ -315,6 +322,13 @@ final class AddEditCouponViewModel: ObservableObject {
315322
// MARK: - Helpers
316323
//
317324
private extension AddEditCouponViewModel {
325+
func checkDiscountTypeUpdated(for coupon: Coupon) -> Bool {
326+
guard let initialCoupon = self.coupon else {
327+
return false
328+
}
329+
return coupon.discountType != initialCoupon.discountType
330+
}
331+
318332
func checkUsageRestrictionsUpdated(for coupon: Coupon) -> Bool {
319333
guard let initialCoupon = self.coupon else {
320334
return false
@@ -384,6 +398,7 @@ private extension AddEditCouponViewModel {
384398

385399
func trackCouponUpdateInitiated(with coupon: Coupon) {
386400
ServiceLocator.analytics.track(.couponUpdateInitiated, withProperties: [
401+
"discount_type_updated": checkDiscountTypeUpdated(for: coupon),
387402
"coupon_code_updated": checkCouponCodeUpdated(for: coupon),
388403
"amount_updated": checkAmountUpdated(for: coupon),
389404
"description_updated": checkDescriptionUpdated(for: coupon),
@@ -457,5 +472,7 @@ private extension AddEditCouponViewModel {
457472
"Edit Product Categories (%1$d)",
458473
comment: "Button for specify the product categories where a coupon can be applied in the view for adding or editing a coupon. " +
459474
"Reads like: Edit Categories")
475+
static let createCouponTitle = NSLocalizedString("Create coupon", comment: "Title of the Create coupon screen")
476+
static let editCouponTitle = NSLocalizedString("Edit coupon", comment: "Title of the Edit coupon screen")
460477
}
461478
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import Foundation
2+
import Yosemite
3+
4+
/// `BottomSheetListSelectorCommand` for selecting a discount type for a coupon.
5+
///
6+
final class DiscountTypeBottomSheetListSelectorCommand: BottomSheetListSelectorCommand {
7+
typealias Model = Coupon.DiscountType
8+
typealias Cell = ImageAndTitleAndTextTableViewCell
9+
10+
let data: [Coupon.DiscountType] = [
11+
.percent,
12+
.fixedCart,
13+
.fixedProduct
14+
]
15+
16+
var selected: Coupon.DiscountType? = nil
17+
18+
private let onSelection: (Coupon.DiscountType) -> Void
19+
20+
init(selected: Coupon.DiscountType?, onSelection: @escaping (Coupon.DiscountType) -> Void) {
21+
self.onSelection = onSelection
22+
self.selected = selected
23+
}
24+
25+
func configureCell(cell: ImageAndTitleAndTextTableViewCell, model: Coupon.DiscountType) {
26+
let viewModel = ImageAndTitleAndTextTableViewCell.ViewModel(title: model.localizedName,
27+
text: model.actionSheetDescription,
28+
image: model.actionSheetIcon,
29+
imageTintColor: .gray(.shade20),
30+
numberOfLinesForText: 0,
31+
isSelected: isSelected(model: model),
32+
isActionable: false)
33+
cell.updateUI(viewModel: viewModel)
34+
}
35+
36+
func handleSelectedChange(selected: Coupon.DiscountType) {
37+
onSelection(selected)
38+
}
39+
40+
func isSelected(model: Coupon.DiscountType) -> Bool {
41+
return model == selected
42+
}
43+
}

0 commit comments

Comments
 (0)