Skip to content

Commit 1edd4b9

Browse files
authored
Merge pull request #6522 from woocommerce/issue/6487-coupon-details-update
Coupons: Update coupon details screen following the updated design
2 parents ed18c80 + 5566b94 commit 1edd4b9

File tree

7 files changed

+262
-222
lines changed

7 files changed

+262
-222
lines changed

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
8.9
44
-----
55
- [*] Coupons: Fixed issue loading the coupon list from the local storage on initial load. [https://github.com/woocommerce/woocommerce-ios/pull/6463]
6+
- [*] Coupons: Update layout of the coupon details screen. [https://github.com/woocommerce/woocommerce-ios/pull/6522]
67
- [*] In-Person Payments: Removed collecting L2/L3 data. [https://github.com/woocommerce/woocommerce-ios/pull/6519]
78
- [*] Hub Menu: Multiple menu items can no longer be tapped simultaneously. [https://github.com/woocommerce/woocommerce-ios/pull/6484]
89
- [*] Jetpack CP: Fixed crash when attempting to access WP-Admin with an invalid URL that has an unsupported scheme. [https://github.com/woocommerce/woocommerce-ios/pull/6502]

WooCommerce/Classes/Model/Coupon+Woo.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ extension Coupon {
115115

116116
/// Formatted amount for the coupon
117117
///
118-
private func formattedAmount(currencySettings: CurrencySettings) -> String {
118+
func formattedAmount(currencySettings: CurrencySettings) -> String {
119119
var amountString: String = ""
120120
switch discountType {
121121
case .percent:

WooCommerce/Classes/ViewRelated/Coupons/CouponDetails/CouponDetails.swift

Lines changed: 122 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ final class CouponDetailsHostingController: UIHostingController<CouponDetails> {
99
super.init(rootView: CouponDetails(viewModel: viewModel, onDeletion: onDeletion))
1010
// The navigation title is set here instead of the SwiftUI view's `navigationTitle`
1111
// to avoid the blinking of the title label when pushed from UIKit view.
12-
title = NSLocalizedString("Coupon", comment: "Title of Coupon Details screen")
12+
title = viewModel.couponCode
1313

1414
// Set presenting view controller to show the notice presenter here
1515
rootView.noticePresenter.presentingViewController = self
@@ -50,16 +50,6 @@ struct CouponDetails: View {
5050
ServiceLocator.analytics.track(.couponDetails, withProperties: ["action": "loaded"])
5151
}
5252

53-
private var detailRows: [DetailRow] {
54-
[
55-
.init(title: Localization.couponCode, content: viewModel.couponCode, action: {}),
56-
.init(title: Localization.description, content: viewModel.description, action: {}),
57-
.init(title: Localization.discount, content: viewModel.amount, action: {}),
58-
.init(title: Localization.applyTo, content: viewModel.productsAppliedTo, action: {}),
59-
.init(title: Localization.expiryDate, content: viewModel.expiryDate, action: {})
60-
]
61-
}
62-
6353
private var actionSheetButtons: [Alert.Button] {
6454
var buttons: [Alert.Button] =
6555
[
@@ -111,11 +101,41 @@ struct CouponDetails: View {
111101
ShareSheet(activityItems: [viewModel.shareMessage])
112102
}
113103

104+
VStack(alignment: .leading, spacing: Constants.verticalSpacing) {
105+
Text(viewModel.couponCode)
106+
.font(.title2)
107+
.bold()
108+
StatusView(label: viewModel.expiryStatus,
109+
backgroundColor: viewModel.expiryStatusBackgroundColor)
110+
}
111+
.padding(.horizontal, insets: geometry.safeAreaInsets)
112+
.padding(.horizontal, Constants.margin)
113+
.padding(.vertical, Constants.summarySectionVerticalSpacing)
114+
115+
Text(viewModel.description)
116+
.bold()
117+
.footnoteStyle()
118+
.renderedIf(viewModel.description.isNotEmpty)
119+
.padding(.horizontal, insets: geometry.safeAreaInsets)
120+
.padding(.horizontal, Constants.margin)
121+
.padding(.bottom, Constants.summarySectionVerticalSpacing)
122+
123+
Divider()
124+
125+
summarySection
126+
.padding(.horizontal, insets: geometry.safeAreaInsets)
127+
.frame(maxWidth: .infinity, alignment: .leading)
128+
.padding(.bottom, Constants.margin)
129+
.background(Color(.listForeground))
130+
131+
Divider()
132+
Spacer().frame(height: Constants.margin)
133+
Divider()
134+
114135
VStack(alignment: .leading, spacing: 0) {
115136
Text(Localization.performance)
116137
.bold()
117138
.padding(Constants.margin)
118-
.padding(.horizontal, insets: geometry.safeAreaInsets)
119139
VStack(alignment: .leading, spacing: Constants.verticalSpacing) {
120140
HStack(alignment: .firstTextBaseline) {
121141
Text(Localization.discountedOrders)
@@ -151,51 +171,18 @@ struct CouponDetails: View {
151171
}
152172
}
153173
}
174+
.padding(.horizontal, insets: geometry.safeAreaInsets)
154175
.padding(.bottom, Constants.margin)
155176
.background(Color(.listForeground))
156177

157178
Divider()
158-
Spacer().frame(height: Constants.margin)
159-
Divider()
160-
161-
VStack(alignment: .leading, spacing: 0) {
162-
Text(Localization.detailSectionTitle)
163-
.bold()
164-
.padding(Constants.margin)
165-
.padding(.horizontal, insets: geometry.safeAreaInsets)
166-
ForEach(detailRows) { row in
167-
TitleAndValueRow(title: row.title,
168-
value: .content(row.content),
169-
selectionStyle: .none,
170-
action: row.action)
171-
.padding(.vertical, Constants.verticalSpacing)
172-
.padding(.horizontal, insets: geometry.safeAreaInsets)
173-
Divider()
174-
.padding(.leading, Constants.margin)
175-
.padding(.leading, insets: geometry.safeAreaInsets)
176-
}
177-
}
178-
.background(Color(.listForeground))
179-
180-
Spacer().frame(height: Constants.margin)
181-
Divider()
182-
VStack {
183-
NavigationRow(content: {
184-
Text(Localization.usageDetails)
185-
.bodyStyle()
186-
}, action: {
187-
showingUsageDetails = true
188-
}).padding(.horizontal, insets: geometry.safeAreaInsets)
189-
}
190-
.background(Color(.listForeground))
191-
Divider()
192179
}
193180
NavigationLink(destination: CouponUsageDetails(viewModel: .init(coupon: viewModel.coupon)), isActive: $showingUsageDetails) {
194181
EmptyView()
195182
}.hidden()
196183
}
197184
.background(Color(.listBackground))
198-
.ignoresSafeArea(.container, edges: [.horizontal, .bottom])
185+
.ignoresSafeArea(.container, edges: [.horizontal])
199186
.sheet(isPresented: $showingEnableAnalytics) {
200187
EnableAnalyticsView(viewModel: .init(siteID: viewModel.siteID),
201188
presentingController: noticePresenter.presentingViewController,
@@ -230,6 +217,54 @@ struct CouponDetails: View {
230217
.wooNavigationBarStyle()
231218
}
232219

220+
private var summarySection: some View {
221+
VStack(alignment: .leading, spacing: Constants.summarySectionVerticalSpacing) {
222+
Text(Localization.summarySectionTitle)
223+
.bold()
224+
.padding(.top, Constants.margin)
225+
226+
VStack(alignment: .leading, spacing: Constants.summarySectionVerticalSpacing) {
227+
Text(viewModel.discountType)
228+
229+
Text(viewModel.summary)
230+
231+
Text(Localization.individualUse)
232+
.renderedIf(viewModel.individualUseOnly)
233+
234+
Text(Localization.allowsFreeShipping)
235+
.renderedIf(viewModel.allowsFreeShipping)
236+
237+
Text(Localization.excludesSaleItems)
238+
.renderedIf(viewModel.excludeSaleItems)
239+
}
240+
241+
VStack(alignment: .leading, spacing: Constants.summarySectionVerticalSpacing) {
242+
Text(String.localizedStringWithFormat(Localization.minimumSpend, viewModel.minimumAmount))
243+
.renderedIf(viewModel.minimumAmount.isNotEmpty)
244+
245+
Text(String.localizedStringWithFormat(Localization.maximumSpend, viewModel.maximumAmount))
246+
.renderedIf(viewModel.maximumAmount.isNotEmpty)
247+
248+
Text(String.localizedStringWithFormat(Localization.singularLimitPerUser, viewModel.usageLimitPerUser))
249+
.renderedIf(viewModel.usageLimitPerUser == 1)
250+
251+
Text(String.localizedStringWithFormat(Localization.pluralLimitPerUser, viewModel.usageLimitPerUser))
252+
.renderedIf(viewModel.usageLimitPerUser > 1)
253+
}
254+
.renderedIf(viewModel.minimumAmount.isNotEmpty ||
255+
viewModel.maximumAmount.isNotEmpty ||
256+
viewModel.usageLimitPerUser > 0)
257+
258+
Text(String.localizedStringWithFormat(Localization.expiryFormat, viewModel.expiryDate))
259+
.renderedIf(viewModel.expiryDate.isNotEmpty)
260+
261+
Text(String.localizedStringWithFormat(Localization.emailRestriction, viewModel.emailRestrictions.joined(separator: ", ")))
262+
.renderedIf(viewModel.emailRestrictions.isNotEmpty)
263+
}
264+
.bodyStyle()
265+
.padding(.horizontal, Constants.margin)
266+
}
267+
233268
@ViewBuilder
234269
private var amountTitleView: some View {
235270
Text(Localization.amount)
@@ -296,15 +331,49 @@ private extension CouponDetails {
296331
static let verticalSpacing: CGFloat = 8
297332
static let errorIconSize: CGFloat = 20
298333
static let errorIconHorizontalPadding: CGFloat = 4
334+
static let summarySectionVerticalSpacing: CGFloat = 24
299335
}
300336

301337
enum Localization {
302-
static let detailSectionTitle = NSLocalizedString("Coupon Details", comment: "Title of Details section in Coupon Details screen")
303-
static let couponCode = NSLocalizedString("Coupon Code", comment: "Title of the Coupon Code row in Coupon Details screen")
304-
static let description = NSLocalizedString("Description", comment: "Title of the Description row in Coupon Details screen")
305-
static let discount = NSLocalizedString("Discount", comment: "Title of the Discount row in Coupon Details screen")
306-
static let applyTo = NSLocalizedString("Apply To", comment: "Title of the Apply To row in Coupon Details screen")
307-
static let expiryDate = NSLocalizedString("Coupon Expiry Date", comment: "Title of the Coupon Expiry Date row in Coupon Details screen")
338+
static let summarySectionTitle = NSLocalizedString("Coupon Summary", comment: "Title of Summary section in Coupon Details screen")
339+
static let expiryFormat = NSLocalizedString(
340+
"Expires %1$@",
341+
comment: "Formatted content for coupon expiry date, reads like: Expires August 4, 2022"
342+
)
343+
static let allowsFreeShipping = NSLocalizedString(
344+
"Allows free shipping",
345+
comment: "Text on Coupon Details screen to indicate that the coupon allows free shipping"
346+
)
347+
static let excludesSaleItems = NSLocalizedString(
348+
"Excludes sale items",
349+
comment: "Text on Coupon Details screen to indicate that the coupon can not be applied to sale items"
350+
)
351+
static let individualUse = NSLocalizedString(
352+
"Individual use only",
353+
comment: "Text on Coupon Details screen to indicate that the coupon can not be applied in conjunction with other coupons"
354+
)
355+
static let minimumSpend = NSLocalizedString(
356+
"Minimum spend of %1$@",
357+
comment: "The minimum limit of spending required for a coupon on the Coupon Details screen, reads like: Minimum spend of $20.00"
358+
)
359+
static let maximumSpend = NSLocalizedString(
360+
"Maximum spend of %1$@",
361+
comment: "The maximum limit of spending allowed for a coupon on the Coupon Details screen, reads like: Minimum spend of $20.00"
362+
)
363+
static let singularLimitPerUser = NSLocalizedString(
364+
"%1$d use per user",
365+
comment: "The singular limit of time for each user to apply a coupon, reads like: 1 use per user"
366+
)
367+
static let pluralLimitPerUser = NSLocalizedString(
368+
"%1$d uses per user",
369+
comment: "The plural limit of time for each user to apply a coupon, reads like: 10 uses per user"
370+
)
371+
static let emailRestriction = NSLocalizedString(
372+
"Restricted to customers with emails: %1$@",
373+
comment: "Restriction for customers with specified emails to use a coupon, " +
374+
"reads like: Restricted to customers with emails: *@a8c.com, *@vip.com"
375+
)
376+
308377
static let manageCoupon = NSLocalizedString("Manage Coupon", comment: "Title of the action sheet displayed from the Coupon Details screen")
309378
static let copyCode = NSLocalizedString("Copy Code", comment: "Action title for copying coupon code from the Coupon Details screen")
310379
static let couponCopied = NSLocalizedString("Coupon copied", comment: "Notice message displayed when a coupon code is " +

0 commit comments

Comments
 (0)