Skip to content

Commit 40f79ff

Browse files
[Shipping Labels] Split shipments instructions notice (#15406)
2 parents 489a77a + 44b4cea commit 40f79ff

File tree

8 files changed

+120
-16
lines changed

8 files changed

+120
-16
lines changed

WooCommerce/Classes/Tools/Notices/DefaultNoticePresenter.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,9 @@ private extension UNMutableNotificationContent {
380380
convenience init(notice: Notice) {
381381
self.init()
382382

383-
title = notice.notificationInfo?.title ?? notice.title
383+
if let title = notice.notificationInfo?.title ?? notice.title {
384+
self.title = title
385+
}
384386

385387
if let body = notice.notificationInfo?.body {
386388
self.body = body

WooCommerce/Classes/Tools/Notices/Notice.swift

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ struct Notice {
99

1010
/// The title that contains the reason for the notice
1111
///
12-
let title: String
12+
let title: String?
1313

1414
/// An optional subtitle that contains a secondary description of the reason for the notice
1515
///
@@ -35,7 +35,6 @@ struct Notice {
3535
///
3636
let actionHandler: (() -> Void)?
3737

38-
3938
/// Designated Initializer
4039
///
4140
init(title: String,
@@ -65,3 +64,41 @@ extension Notice: Equatable {
6564
lhs.actionTitle == rhs.actionTitle
6665
}
6766
}
67+
68+
/// Convenience initializers to init without a`title`
69+
/// These initializers help to ensure that at least one of title/subtitle/message is non-nil.
70+
///
71+
extension Notice {
72+
/// To init using `subtitle` and/or `message`
73+
///
74+
init(subtitle: String,
75+
message: String? = nil,
76+
feedbackType: UINotificationFeedbackGenerator.FeedbackType? = nil,
77+
notificationInfo: NoticeNotificationInfo? = nil,
78+
actionTitle: String? = nil,
79+
actionHandler: ((() -> Void))? = nil) {
80+
self.title = nil
81+
self.subtitle = subtitle
82+
self.message = message
83+
self.feedbackType = feedbackType
84+
self.notificationInfo = notificationInfo
85+
self.actionTitle = actionTitle
86+
self.actionHandler = actionHandler
87+
}
88+
89+
/// To init using only `message`
90+
///
91+
init(message: String,
92+
feedbackType: UINotificationFeedbackGenerator.FeedbackType? = nil,
93+
notificationInfo: NoticeNotificationInfo? = nil,
94+
actionTitle: String? = nil,
95+
actionHandler: ((() -> Void))? = nil) {
96+
self.title = nil
97+
self.subtitle = nil
98+
self.message = message
99+
self.feedbackType = feedbackType
100+
self.notificationInfo = notificationInfo
101+
self.actionTitle = actionTitle
102+
self.actionHandler = actionHandler
103+
}
104+
}

WooCommerce/Classes/View Modifiers/View+NoticesModifier.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,15 @@ struct NoticeModifier: ViewModifier {
6464
Spacer()
6565
HStack(spacing: 0.0) {
6666
VStack {
67-
HStack {
68-
Text(notice.title)
69-
.lineLimit(notice.message.isNilOrEmpty ? 0 : 2)
70-
Spacer()
67+
if let title = notice.title {
68+
HStack {
69+
Text(title)
70+
.lineLimit(notice.message.isNilOrEmpty ? 0 : 2)
71+
Spacer()
72+
}
73+
.font(Constants.titleFont)
74+
.foregroundColor(Constants.titleColor)
7175
}
72-
.font(Constants.titleFont)
73-
.foregroundColor(Constants.titleColor)
7476
if let subtitle = notice.subtitle {
7577
HStack {
7678
Text(subtitle)
@@ -81,7 +83,7 @@ struct NoticeModifier: ViewModifier {
8183
}
8284
if let message = notice.message {
8385
HStack {
84-
Text(message)
86+
BoldableTextView(message)
8587
Spacer()
8688
}
8789
.font(Constants.messageFont)

WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/CollapsibleShipmentCardViewModel.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ final class CollapsibleShipmentCardViewModel: ObservableObject, Identifiable {
1212
/// Child shipment rows, if the shipment has more than one quantity
1313
let childShipmentRows: [SelectableShipmentRowViewModel]
1414

15+
var onSelectionChange: (() -> Void)?
16+
17+
var hasSelectedAnItem: Bool {
18+
if mainShipmentRow.selected {
19+
return true
20+
}
21+
22+
return childShipmentRows
23+
.filter { $0.selected }
24+
.isNotEmpty
25+
}
26+
1527
init(parentShipmentId: String,
1628
childShipmentIds: [String],
1729
item: ShippingLabelPackageItem,
@@ -47,13 +59,15 @@ private extension CollapsibleShipmentCardViewModel {
4759
guard let self else { return }
4860

4961
childShipmentRows.forEach({ $0.setSelected(row.selected) })
62+
onSelectionChange?()
5063
}
5164

5265
childShipmentRows.forEach({
5366
$0.onSelectedChange = { [weak self] row in
5467
guard let self else { return }
5568

5669
mainShipmentRow.setSelected(false)
70+
onSelectionChange?()
5771
}
5872
})
5973
}

WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/WooShippingSplitShipmentsDetailView.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Yosemite
44
struct WooShippingSplitShipmentsDetailView: View {
55
@Environment(\.dismiss) private var dismiss
66

7-
let viewModel: WooShippingSplitShipmentsViewModel
7+
@ObservedObject var viewModel: WooShippingSplitShipmentsViewModel
88

99
var body: some View {
1010
NavigationView {
@@ -41,6 +41,10 @@ struct WooShippingSplitShipmentsDetailView: View {
4141
}
4242
}
4343
}
44+
.notice($viewModel.instructionsNotice, autoDismiss: false)
45+
.onAppear {
46+
viewModel.onAppear()
47+
}
4448
}
4549
}
4650

WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/WooShippingSplitShipmentsViewModel.swift

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ final class WooShippingSplitShipmentsViewModel: ObservableObject {
2626
"\(itemsWeightLabel)\(itemsPriceLabel)"
2727
}
2828

29-
@Published var selectedItemIDs: [String: [String]] = [:]
30-
3129
let shipmentCardViewModels: [CollapsibleShipmentCardViewModel]
3230

31+
@Published var instructionsNotice: Notice?
32+
3333
init(order: Order,
3434
config: WooShippingConfig,
3535
items: [ShippingLabelPackageItem],
@@ -70,6 +70,11 @@ final class WooShippingSplitShipmentsViewModel: ObservableObject {
7070
}()
7171

7272
configureSectionHeader()
73+
configureSelectionCallback()
74+
}
75+
76+
func onAppear() {
77+
showInstructionsNotice()
7378
}
7479

7580
func selectAll() {
@@ -80,6 +85,34 @@ final class WooShippingSplitShipmentsViewModel: ObservableObject {
8085
}
8186

8287
private extension WooShippingSplitShipmentsViewModel {
88+
func configureSelectionCallback() {
89+
shipmentCardViewModels.forEach { viewModel in
90+
viewModel.onSelectionChange = { [weak self] in
91+
self?.checkSelectionAndHideInstructions()
92+
}
93+
}
94+
}
95+
96+
func showInstructionsNotice() {
97+
if hasSelectedAnItem() == false {
98+
instructionsNotice = Notice(message: Localization.SelectionInstructionsNotice.message,
99+
feedbackType: .success,
100+
actionTitle: Localization.SelectionInstructionsNotice.dismiss) { [weak self] in
101+
self?.instructionsNotice = nil
102+
}
103+
}
104+
}
105+
106+
func checkSelectionAndHideInstructions() {
107+
if hasSelectedAnItem() {
108+
instructionsNotice = nil
109+
}
110+
}
111+
112+
func hasSelectedAnItem() -> Bool {
113+
shipmentCardViewModels.contains(where: { $0.hasSelectedAnItem })
114+
}
115+
83116
/// Configures the labels in the section header.
84117
///
85118
func configureSectionHeader() {
@@ -113,6 +146,16 @@ private extension WooShippingSplitShipmentsViewModel {
113146
// MARK: Constants
114147
private extension WooShippingSplitShipmentsViewModel {
115148
enum Localization {
149+
enum SelectionInstructionsNotice {
150+
static let message = NSLocalizedString("wooShipping.createLabels.splitShipment.SelectionInstructionsNotice.message",
151+
value: "To split, select the items, and tap **move to new shipment** when the toolbar appears.",
152+
comment: "Instructions to ask customer to select items to split during shipping label creation."
153+
+ " The content inside two double asterisks **...** denote bolded text.")
154+
155+
static let dismiss = NSLocalizedString("wooShipping.createLabels.splitShipment.SelectionInstructionsNotice.dismiss",
156+
value: "Dismiss",
157+
comment: "Label of the button to dismiss the instructions notice in split shipments flow.")
158+
}
116159
static func itemsCount(_ count: Decimal) -> String {
117160
return String.pluralize(count, singular: Localization.itemsCountSingularFormat, plural: Localization.itemsCountPluralFormat)
118161
}

WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private extension WooShippingCreateLabelsView {
104104
WooShippingPostPurchaseView(viewModel: postPurchase)
105105
}
106106

107-
if let splitShipmentsViewModel = viewModel.splitShipmentsViewModel {
107+
if !viewModel.canViewLabel, let splitShipmentsViewModel = viewModel.splitShipmentsViewModel {
108108
WooShippingSplitShipmentsRow(viewModel: splitShipmentsViewModel)
109109
}
110110

WooCommerce/WooCommerceTests/Authentication/Epilogue/SwitchStoreNoticePresenterTests.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ final class SwitchStoreNoticePresenterTests: XCTestCase {
6666
XCTAssertEqual(noticePresenter.queuedNotices.count, 1)
6767

6868
let notice = try XCTUnwrap(noticePresenter.queuedNotices.first)
69-
assertThat(notice.title, contains: siteName)
69+
let title = try XCTUnwrap(notice.title)
70+
assertThat(title, contains: siteName)
7071
let expectedTitle = String.localizedStringWithFormat(SwitchStoreNoticePresenter.Localization.titleFormat, site.name)
7172
XCTAssertEqual(notice.title, expectedTitle)
7273
}
@@ -92,7 +93,8 @@ final class SwitchStoreNoticePresenterTests: XCTestCase {
9293
XCTAssertEqual(noticePresenter.queuedNotices.count, 1)
9394

9495
let notice = try XCTUnwrap(noticePresenter.queuedNotices.first)
95-
assertThat(notice.title, contains: siteName)
96+
let title = try XCTUnwrap(notice.title)
97+
assertThat(title, contains: siteName)
9698
let expectedTitle = String.localizedStringWithFormat(SwitchStoreNoticePresenter.Localization.titleFormat, site.name)
9799
XCTAssertEqual(notice.title, expectedTitle)
98100
}

0 commit comments

Comments
 (0)