Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ struct WooShippingHazmatDetailView: View {

@State private var isShowingCategoryList = false

init(isHazardous: Bool, selectedCategory: ShippingLabelHazmatCategory?) {
self.isHazardous = isHazardous
private let selectionHandler: (ShippingLabelHazmatCategory?) -> Void

init(selectedCategory: ShippingLabelHazmatCategory?,
selectionHandler: @escaping (ShippingLabelHazmatCategory?) -> Void) {
self.isHazardous = selectedCategory != nil
self.selectedCategory = selectedCategory
self.selectionHandler = selectionHandler
}

var body: some View {
Expand All @@ -29,6 +33,7 @@ struct WooShippingHazmatDetailView: View {
Toggle(isOn: $isHazardous) {
Text(Localization.toggleLabel)
}
.tint(Color.accentColor)

Button(Localization.selectCategory) {
isShowingCategoryList = true
Expand Down Expand Up @@ -65,7 +70,8 @@ struct WooShippingHazmatDetailView: View {
.sheet(isPresented: $isShowingCategoryList) {
WooShippingHazmatCategoryList(selectedItem: selectedCategory,
selectionHandler: { category in
// TODO: dismiss view
selectionHandler(category)
dismiss()
})
}
}
Expand Down Expand Up @@ -177,6 +183,5 @@ private extension WooShippingHazmatDetailView {
}

#Preview {
WooShippingHazmatDetailView(isHazardous: true,
selectedCategory: .airEligibleEthanol)
WooShippingHazmatDetailView(selectedCategory: .airEligibleEthanol, selectionHandler: { _ in })
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,53 @@ struct WooShippingHazmatRow: View {
/// Whether the interactions (navigation/setting selection) are enabled.
private let enabled: Bool

@Binding private var isHazardous: Bool

@Binding private var selectedCategory: ShippingLabelHazmatCategory?

@State private var isShowingDetailView = false

init(isHazardous: Binding<Bool>,
selectedCategory: Binding<ShippingLabelHazmatCategory?>,
init(selectedCategory: Binding<ShippingLabelHazmatCategory?>,
enabled: Bool) {
self._isHazardous = isHazardous
self._selectedCategory = selectedCategory
self.enabled = enabled
}

var body: some View {
Button(action: {
isShowingDetailView = true
}) {
AdaptiveStack {
Text(Localization.hazmatLabel)
.bodyStyle()
Spacer()
Text(isHazardous ? Localization.yes : Localization.no)
.secondaryBodyStyle()
Image(uiImage: .chevronImage)
.secondaryBodyStyle()
.renderedIf(enabled)
VStack {
Button(action: {
isShowingDetailView = true
}) {
AdaptiveStack {
Text(Localization.hazmatLabel)
.bodyStyle()
Spacer()
Text(selectedCategory != nil ? Localization.yes : Localization.no)
.secondaryBodyStyle()
Image(uiImage: .chevronImage)
.secondaryBodyStyle()
.renderedIf(enabled)
}
}
.buttonStyle(.plain)
.disabled(!enabled)

if let category = selectedCategory {
Text(category.localizedName)
.captionStyle()
.frame(maxWidth: .infinity, alignment: .leading)
.multilineTextAlignment(.leading)
.padding(Layout.categoryPadding)
.background(
Color(.quaternarySystemFill)
.clipShape(RoundedRectangle(cornerSize: .init(width: Layout.backgroundRadius,
height: Layout.backgroundRadius)))
)
}
.padding(.vertical, Layout.verticalPadding)
}
.buttonStyle(.plain)
.disabled(!enabled)
.padding(.vertical, Layout.verticalPadding)
.sheet(isPresented: $isShowingDetailView) {
WooShippingHazmatDetailView(isHazardous: isHazardous,
selectedCategory: selectedCategory)
WooShippingHazmatDetailView(selectedCategory: selectedCategory) { selectedCategory in
self.selectedCategory = selectedCategory
}
}
}
}
Expand All @@ -47,6 +59,7 @@ private extension WooShippingHazmatRow {
enum Layout {
static let backgroundRadius: CGFloat = 8
static let verticalPadding: CGFloat = 24
static let categoryPadding: CGFloat = 16
}

enum Localization {
Expand All @@ -67,8 +80,7 @@ private extension WooShippingHazmatRow {
}

#Preview {
WooShippingHazmatRow(isHazardous: .constant(false),
selectedCategory: .constant(nil),
WooShippingHazmatRow(selectedCategory: .constant(nil),
enabled: true)
.padding()
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ struct WooShippingCreateLabelsView: View {
}
}
.safeAreaInset(edge: .bottom) {
if viewModel.state == .ready {
if viewModel.state == .ready && viewModel.hazmatNotice == nil {
expandableBottomSheet
}
}
Expand Down Expand Up @@ -92,6 +92,7 @@ struct WooShippingCreateLabelsView: View {
WooShippingCustomsForm(viewModel: viewModel.customsFormViewModel)
}
.notice($viewModel.labelPurchaseErrorNotice, autoDismiss: false)
.notice($viewModel.hazmatNotice)
}
}
}
Expand All @@ -110,8 +111,7 @@ private extension WooShippingCreateLabelsView {

WooShippingItems(viewModel: viewModel.items)

WooShippingHazmatRow(isHazardous: $viewModel.containsHazardousMaterials,
selectedCategory: $viewModel.hazmatCategory,
WooShippingHazmatRow(selectedCategory: $viewModel.hazmatCategory,
enabled: !viewModel.canViewLabel)

WooShippingCustomsRow(informationIsCompleted: viewModel.customsInformationIsCompleted,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ final class WooShippingCreateLabelsViewModel: ObservableObject {
private var subscriptions: Set<AnyCancellable> = []
private var debounceDuration: Double = 1

@Published var containsHazardousMaterials = false
@Published var hazmatCategory: ShippingLabelHazmatCategory?
@Published var hazmatNotice: Notice?

@Published var labelPurchaseErrorNotice: Notice?

Expand Down Expand Up @@ -229,6 +229,7 @@ final class WooShippingCreateLabelsViewModel: ObservableObject {
observeSelectedPackage()
observeForLabelRates()
observeForCustomsForm()
observeHAZMATChanges()
Task {
await loadRequiredData()
}
Expand Down Expand Up @@ -570,6 +571,23 @@ private extension WooShippingCreateLabelsViewModel {
.store(in: &subscriptions)
}

func observeHAZMATChanges() {
$hazmatCategory
.dropFirst()
.scan((nil, nil)) { (previous: (current: ShippingLabelHazmatCategory?,
previous: ShippingLabelHazmatCategory?),
newValue: ShippingLabelHazmatCategory?) in
return (current: newValue, previous: previous.current)
}
.map { [weak self] (newValue, oldValue) in
let noticeTitle = newValue != nil ? Localization.hazmatSet : Localization.hazmatRemoved
return Notice(title: noticeTitle, actionTitle: Localization.undo, actionHandler: {
self?.hazmatCategory = oldValue
})
}
.assign(to: &$hazmatNotice)
}

func observeForCustomsForm() {
$selectedOriginAddress.combineLatest($destinationAddress)
.map { (originAddress, destinationAddress) -> Bool in
Expand Down Expand Up @@ -632,7 +650,7 @@ private extension WooShippingCreateLabelsViewModel {
height: Double(packageData.height) ?? 0,
weight: weight,
isLetter: WooShippingPackageType(rawValue: packageData.packageType) == .envelope,
hazmatCategory: nil, // Hazmat support will be added in a future milestone
hazmatCategory: hazmatCategory?.rawValue,
customsForm: customsForm)
}
}
Expand Down Expand Up @@ -696,6 +714,24 @@ private extension WooShippingCreateLabelsViewModel {
value: "Retry",
comment: "Button to retry label purchase when an error occurs")
}

static let hazmatSet = NSLocalizedString(
"wooShipping.createLabels.hazmatSet",
value: "Hazardous materials category set",
comment: "Notice when a hazardous materials category is set on the shipping label creation screen"
)

static let hazmatRemoved = NSLocalizedString(
"wooShipping.createLabels.hazmatRemoved",
value: "Remove hazardous materials category",
comment: "Notice when a hazardous materials category is removed on the shipping label creation screen"
)

static let undo = NSLocalizedString(
"wooShipping.createLabels.undo",
value: "Undo",
comment: "Button to undo a change on the shipping label creation screen"
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,41 @@ final class WooShippingCreateLabelsViewModelTests: XCTestCase {
XCTAssertFalse(viewModel.isPurchasingLabel)
}

func test_purchaseLabel_sets_hazmat_category_correctly() {
// Given
var encodedHazmat: [String: Any]?
let stores = MockStoresManager(sessionManager: .testingInstance)
let viewModel = WooShippingCreateLabelsViewModel(order: Order.fake().copy(shippingAddress: Address.fake()),
selectedOriginAddress: WooShippingOriginAddress.fake(),
selectedPackage: samplePackageData(),
selectedRate: sampleSelectedRate(),
stores: stores)
stores.whenReceivingAction(ofType: WooShippingAction.self) { action in
switch action {
case let .purchaseShippingLabel(_, _, _, _, package, _, _, _, completion):
encodedHazmat = package.encodedHazmat()
completion(.success(ShippingLabel.fake()))
case let .loadLabelRates(_, _, _, _, packages, completion):
completion(packages, .success([]))
case .loadAccountSettings(_, let completion):
completion(.success(self.settings))
case .loadPackages, .loadOriginAddresses, .verifyDestinationAddress, .loadConfig:
break
default:
XCTFail("Unexpected action: \(action)")
}
}

// When
viewModel.hazmatCategory = .class3
viewModel.purchaseLabel()

// Then
let shipmentDetails = encodedHazmat?["shipment_0"] as? [String: Any]
XCTAssertEqual(shipmentDetails?["isHazmat"] as? Bool, true)
XCTAssertEqual(shipmentDetails?["category"] as? String, ShippingLabelHazmatCategory.class3.rawValue)
}

func test_selectPackage_sets_selectedPackage_with_package_data() {
// Given
let viewModel = WooShippingCreateLabelsViewModel(order: Order.fake())
Expand Down Expand Up @@ -924,6 +959,20 @@ final class WooShippingCreateLabelsViewModelTests: XCTestCase {
// Then
XCTAssertNotNil(viewModel.addressToEdit)
}

func test_hazmatNotice_is_updated_after_setting_new_hazmat_category() {
// Given
let viewModel = WooShippingCreateLabelsViewModel(order: Order.fake())
XCTAssertNil(viewModel.hazmatNotice)

// When
viewModel.hazmatCategory = .class1

// Then
waitUntil {
viewModel.hazmatNotice != nil
}
}
}

private extension WooShippingCreateLabelsViewModelTests {
Expand Down