Skip to content

Commit f774bf9

Browse files
authored
[Shipping labels] Add "missing address" label and notice for destination address (#15081)
2 parents 660b4dd + 3d2970b commit f774bf9

File tree

3 files changed

+135
-50
lines changed

3 files changed

+135
-50
lines changed

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

Lines changed: 54 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ struct WooShippingCreateLabelsView: View {
3737
/// Whether the origin address list sheet is presented.
3838
@State private var isOriginAddressListPresented = false
3939

40+
/// Whether the destination address is verified.
41+
private var isDestinationAddressVerified: Bool {
42+
viewModel.destinationAddressStatus == .verified
43+
}
44+
4045
var body: some View {
4146
NavigationStack {
4247
ScrollView {
@@ -143,12 +148,10 @@ private extension WooShippingCreateLabelsView {
143148
Text(Localization.BottomSheet.shipmentDetails)
144149
.foregroundStyle(Color(.primary))
145150
.bold()
146-
if viewModel.showAddressVerificationNotice {
147-
addressVerificationNotice
148-
.onTapGesture {
149-
// TODO: Start address editing/verification flow if needed (if destination address is unverified).
150-
}
151-
}
151+
addressVerificationNotice(with: viewModel.destinationAddressStatusNoticeLabel)
152+
.onTapGesture {
153+
// TODO: Start address editing/verification flow if needed (if destination address is unverified).
154+
}
152155
}
153156
}
154157

@@ -210,11 +213,13 @@ private extension WooShippingCreateLabelsView {
210213
Text(Localization.BottomSheet.shipTo)
211214
.frame(width: shipmentDetailsShipFromSize.width, alignment: .leading)
212215
VStack(alignment: .leading) {
213-
ForEach(viewModel.destinationAddressLines, id: \.self) { addressLine in
214-
Text(addressLine)
215-
.if(addressLine == viewModel.destinationAddressLines.first) { line in
216-
line.bold()
217-
}
216+
if let addressLines = viewModel.destinationAddressLines {
217+
ForEach(addressLines, id: \.self) { addressLine in
218+
Text(addressLine)
219+
.if(addressLine == addressLines.first) { line in
220+
line.bold()
221+
}
222+
}
218223
}
219224
addressVerificationLabel
220225
}
@@ -296,39 +301,40 @@ private extension WooShippingCreateLabelsView {
296301
.disabled(!viewModel.isPurchaseButtonEnabled)
297302
}
298303

299-
/// View showing the address verification status.
304+
/// View showing the address verification status for a destination address.
300305
var addressVerificationLabel: some View {
301306
HStack(spacing: 4) {
302-
Image(systemName: viewModel.isDestinationAddressVerified ? "checkmark.circle" : "exclamationmark.circle")
303-
Text(viewModel.isDestinationAddressVerified
304-
? Localization.AddressVerification.verified : Localization.AddressVerification.unverified)
307+
Image(systemName: isDestinationAddressVerified ? "checkmark.circle" : "exclamationmark.circle")
308+
Text(Localization.AddressVerification.label(for: viewModel.destinationAddressStatus))
305309
}
306310
.font(.subheadline)
307-
.foregroundStyle(viewModel.isDestinationAddressVerified ? Layout.green : Layout.red)
311+
.foregroundStyle(isDestinationAddressVerified ? Layout.green : Layout.red)
308312
}
309313

310-
/// View showing a notice about the address verification status.
311-
var addressVerificationNotice: some View {
312-
HStack(spacing: 8) {
313-
Image(systemName: viewModel.isDestinationAddressVerified ? "checkmark.circle" : "exclamationmark.circle")
314-
Text(viewModel.isDestinationAddressVerified
315-
? Localization.AddressVerification.destinationVerified : Localization.AddressVerification.destinationUnverified)
316-
.frame(maxWidth: .infinity, alignment: .leading)
317-
Button {
318-
withAnimation {
319-
viewModel.showAddressVerificationNotice = false
314+
/// View showing a notice about the destination address verification status.
315+
@ViewBuilder
316+
func addressVerificationNotice(with label: String?) -> some View {
317+
if let label = viewModel.destinationAddressStatusNoticeLabel {
318+
HStack(spacing: 8) {
319+
Image(systemName: isDestinationAddressVerified ? "checkmark.circle" : "exclamationmark.circle")
320+
Text(label)
321+
.frame(maxWidth: .infinity, alignment: .leading)
322+
Button {
323+
withAnimation {
324+
viewModel.destinationAddressStatusNoticeLabel = nil
325+
}
326+
} label: {
327+
Image(systemName: "xmark")
328+
.renderedIf(!isDestinationAddressVerified)
320329
}
321-
} label: {
322-
Image(systemName: "xmark")
323-
.renderedIf(!viewModel.isDestinationAddressVerified)
324330
}
331+
.font(.subheadline)
332+
.foregroundStyle(isDestinationAddressVerified ? Layout.green : Layout.red)
333+
.padding(.horizontal, 16)
334+
.padding(.vertical, 12)
335+
.background(RoundedRectangle(cornerRadius: Layout.cornerRadius)
336+
.fill(Color(uiColor: isDestinationAddressVerified ? .withColorStudio(.green, shade: .shade0) : .withColorStudio(.red, shade: .shade0))))
325337
}
326-
.font(.subheadline)
327-
.foregroundStyle(viewModel.isDestinationAddressVerified ? Layout.green : Layout.red)
328-
.padding(.horizontal, 16)
329-
.padding(.vertical, 12)
330-
.background(RoundedRectangle(cornerRadius: Layout.cornerRadius)
331-
.fill(Color(uiColor: viewModel.isDestinationAddressVerified ? .withColorStudio(.green, shade: .shade0) : .withColorStudio(.red, shade: .shade0))))
332338
}
333339
}
334340

@@ -425,18 +431,25 @@ private extension WooShippingCreateLabelsView {
425431
}
426432

427433
enum AddressVerification {
434+
static func label(for status: WooShippingCreateLabelsViewModel.DestinationAddressStatus) -> String {
435+
switch status {
436+
case .verified:
437+
return verified
438+
case .unverified:
439+
return unverified
440+
case .missing:
441+
return missing
442+
}
443+
}
428444
static let verified = NSLocalizedString("wooShipping.createLabels.addressVerification.verified",
429445
value: "Address verified",
430446
comment: "Label when an address is verified on the shipping label creation screen")
431447
static let unverified = NSLocalizedString("wooShipping.createLabels.addressVerification.unverified",
432448
value: "Unverified address",
433449
comment: "Label when an address is unverified on the shipping label creation screen")
434-
static let destinationVerified = NSLocalizedString("wooShipping.createLabels.addressVerification.destinationVerified",
435-
value: "Verified destination address",
436-
comment: "Notice when a destination address is verified on the shipping label creation screen")
437-
static let destinationUnverified = NSLocalizedString("wooShipping.createLabels.addressVerification.destinationUnverified",
438-
value: "Destination address unverified",
439-
comment: "Notice when a destination address is unverified on the shipping label creation screen")
450+
static let missing = NSLocalizedString("wooShipping.createLabels.addressVerification.missing",
451+
value: "Missing address",
452+
comment: "Label when an address is missing on the shipping label creation screen")
440453
}
441454
}
442455
}

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

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,23 @@ final class WooShippingCreateLabelsViewModel: ObservableObject {
6161
@Published private(set) var originAddress: String = ""
6262

6363
/// Address to ship to (customer address), formatted for display and split into separate lines to allow additional formatting.
64-
private(set) lazy var destinationAddressLines: [String] = {
65-
(destinationAddress?.formattedPostalAddress ?? "").components(separatedBy: .newlines)
64+
private(set) lazy var destinationAddressLines: [String]? = {
65+
(destinationAddress?.formattedPostalAddress)?.components(separatedBy: .newlines)
6666
}()
6767

68-
// TODO: Add support for checking if the destination address is verified.
69-
/// Whether the destination address is verified.
70-
@Published private(set) var isDestinationAddressVerified: Bool = false
68+
/// Possible statuses for a Woo Shipping destination address.
69+
enum DestinationAddressStatus {
70+
case verified
71+
case unverified
72+
case missing
73+
}
74+
75+
// TODO: Add support for updating the destination address status when it is edited or verified remotely.
76+
/// The current destination address status.
77+
@Published private(set) var destinationAddressStatus: DestinationAddressStatus = .unverified
7178

72-
// TODO: Set to false if the destination address is already verified.
73-
// TODO: Set to true for a couple seconds after the destination address is verified.
74-
/// Whether the destination address verification notice is displayed.
75-
@Published var showAddressVerificationNotice = true
79+
/// This property can be set to display a notice with the provided label about the destination address status.
80+
@Published var destinationAddressStatusNoticeLabel: String?
7681

7782
/// Shipping lines for the order, with formatted amount.
7883
let shippingLines: [WooShipping_ShippingLineViewModel]
@@ -180,7 +185,11 @@ final class WooShippingCreateLabelsViewModel: ObservableObject {
180185
self.stores = stores
181186
self.debounceDuration = debounceDuration
182187

188+
// TODO: Check remotely to see if the destination address is verified.
189+
destinationAddressStatus = destinationAddressLines == nil ? .missing : .unverified
190+
183191
observeSelectedOriginAddress()
192+
observeDestinationAddressStatus()
184193
observeSelectedPackage()
185194
observeForLabelRates()
186195
loadStoreOptions()
@@ -202,6 +211,7 @@ final class WooShippingCreateLabelsViewModel: ObservableObject {
202211
self.shippingLines = order.shippingLines.map({ WooShipping_ShippingLineViewModel(shippingLine: $0, currency: order.currency) })
203212
self.originAddress = shippingLabel.originAddress.formattedPostalAddress?.replacingOccurrences(of: "\n", with: ", ") ?? ""
204213
self.destinationAddress = shippingLabel.destinationAddress
214+
self.destinationAddressStatus = .verified
205215
self.onLabelPurchase = nil
206216
self.stores = stores
207217
}
@@ -330,6 +340,30 @@ private extension WooShippingCreateLabelsViewModel {
330340
.store(in: &subscriptions)
331341
}
332342

343+
/// Observes the destination address status and updates the notice label.
344+
func observeDestinationAddressStatus() {
345+
/// Set the notice when the destination address status changes.
346+
$destinationAddressStatus
347+
.map { status in
348+
switch status {
349+
case .verified:
350+
return Localization.DestinationAddressStatus.verified
351+
case .unverified:
352+
return Localization.DestinationAddressStatus.unverified
353+
case .missing:
354+
return Localization.DestinationAddressStatus.missing
355+
}
356+
}
357+
.assign(to: &$destinationAddressStatusNoticeLabel)
358+
359+
/// Clear the notice after a delay when the address is verified.
360+
$destinationAddressStatusNoticeLabel
361+
.filter { $0 == Localization.DestinationAddressStatus.verified }
362+
.delay(for: .seconds(2), scheduler: RunLoop.current)
363+
.map { _ in nil }
364+
.assign(to: &$destinationAddressStatusNoticeLabel)
365+
}
366+
333367
/// Observes the selected package and shipment weight and requests the available shipping rates.
334368
func observeForLabelRates() {
335369
$shipmentWeight
@@ -450,6 +484,18 @@ private extension WooShippingCreateLabelsViewModel {
450484
value: "Adult Signature Required",
451485
comment: "Label for row showing the additional cost to require an adult signature " +
452486
"on the shipping label creation screen")
487+
488+
enum DestinationAddressStatus {
489+
static let verified = NSLocalizedString("wooShipping.createLabels.addressVerification.destinationVerified",
490+
value: "Verified destination address",
491+
comment: "Notice when a destination address is verified on the shipping label creation screen")
492+
static let unverified = NSLocalizedString("wooShipping.createLabels.addressVerification.destinationUnverified",
493+
value: "Destination address unverified",
494+
comment: "Notice when a destination address is unverified on the shipping label creation screen")
495+
static let missing = NSLocalizedString("wooShipping.createLabels.addressVerification.destinationMissing",
496+
value: "Destination address missing",
497+
comment: "Notice when a destination address is missing on the shipping label creation screen")
498+
}
453499
}
454500

455501
enum Constants {

WooCommerce/WooCommerceTests/ViewRelated/Shipping Label/WooShipping Create Shipping Labels/WooShippingCreateLabelsViewModelTests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,32 @@ final class WooShippingCreateLabelsViewModelTests: XCTestCase {
473473
// Then
474474
XCTAssertTrue(viewModel.customsInformationIsCompleted)
475475
}
476+
477+
func test_destinationAddressStatus_unverified_and_noticeLabel_set_for_unverified_address() {
478+
// Given
479+
let address = Address.fake().copy(address1: "1 Main Street", city: "San Francisco", state: "CA", postcode: "12345", country: "US")
480+
let order = Order.fake().copy(shippingAddress: address)
481+
482+
// When
483+
let viewModel = WooShippingCreateLabelsViewModel(order: order)
484+
485+
// Then
486+
XCTAssertEqual(viewModel.destinationAddressStatus, .unverified)
487+
XCTAssertNotNil(viewModel.destinationAddressStatusNoticeLabel)
488+
}
489+
490+
func test_destinationAddressStatus_missing_and_noticeLabel_set_for_empty_address() {
491+
// Given
492+
let destinationAddress = Address.fake()
493+
let order = Order.fake().copy(shippingAddress: destinationAddress)
494+
495+
// When
496+
let viewModel = WooShippingCreateLabelsViewModel(order: order)
497+
498+
// Then
499+
XCTAssertEqual(viewModel.destinationAddressStatus, .missing)
500+
XCTAssertNotNil(viewModel.destinationAddressStatusNoticeLabel)
501+
}
476502
}
477503

478504
private extension WooShippingCreateLabelsViewModelTests {

0 commit comments

Comments
 (0)