diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Hazmat Section/WooShippingHazmatDetailView.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Hazmat Section/WooShippingHazmatDetailView.swift index b7100622ded..0ba1db3465b 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Hazmat Section/WooShippingHazmatDetailView.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Hazmat Section/WooShippingHazmatDetailView.swift @@ -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 { @@ -29,6 +33,7 @@ struct WooShippingHazmatDetailView: View { Toggle(isOn: $isHazardous) { Text(Localization.toggleLabel) } + .tint(Color.accentColor) Button(Localization.selectCategory) { isShowingCategoryList = true @@ -65,7 +70,8 @@ struct WooShippingHazmatDetailView: View { .sheet(isPresented: $isShowingCategoryList) { WooShippingHazmatCategoryList(selectedItem: selectedCategory, selectionHandler: { category in - // TODO: dismiss view + selectionHandler(category) + dismiss() }) } } @@ -177,6 +183,5 @@ private extension WooShippingHazmatDetailView { } #Preview { - WooShippingHazmatDetailView(isHazardous: true, - selectedCategory: .airEligibleEthanol) + WooShippingHazmatDetailView(selectedCategory: .airEligibleEthanol, selectionHandler: { _ in }) } diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Hazmat Section/WooShippingHazmatRow.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Hazmat Section/WooShippingHazmatRow.swift index 8b2293380c6..5b2419ee9df 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Hazmat Section/WooShippingHazmatRow.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Hazmat Section/WooShippingHazmatRow.swift @@ -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, - selectedCategory: Binding, + init(selectedCategory: Binding, 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 + } } } } @@ -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 { @@ -67,8 +80,7 @@ private extension WooShippingHazmatRow { } #Preview { - WooShippingHazmatRow(isHazardous: .constant(false), - selectedCategory: .constant(nil), + WooShippingHazmatRow(selectedCategory: .constant(nil), enabled: true) .padding() } diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsView.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsView.swift index 8feb1d61f4f..1847d0bcce0 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsView.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsView.swift @@ -60,7 +60,7 @@ struct WooShippingCreateLabelsView: View { } } .safeAreaInset(edge: .bottom) { - if viewModel.state == .ready { + if viewModel.state == .ready && viewModel.hazmatNotice == nil { expandableBottomSheet } } @@ -92,6 +92,7 @@ struct WooShippingCreateLabelsView: View { WooShippingCustomsForm(viewModel: viewModel.customsFormViewModel) } .notice($viewModel.labelPurchaseErrorNotice, autoDismiss: false) + .notice($viewModel.hazmatNotice) } } } @@ -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, diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsViewModel.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsViewModel.swift index 8378e1d641b..a889a3627da 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsViewModel.swift @@ -21,8 +21,8 @@ final class WooShippingCreateLabelsViewModel: ObservableObject { private var subscriptions: Set = [] private var debounceDuration: Double = 1 - @Published var containsHazardousMaterials = false @Published var hazmatCategory: ShippingLabelHazmatCategory? + @Published var hazmatNotice: Notice? @Published var labelPurchaseErrorNotice: Notice? @@ -229,6 +229,7 @@ final class WooShippingCreateLabelsViewModel: ObservableObject { observeSelectedPackage() observeForLabelRates() observeForCustomsForm() + observeHAZMATChanges() Task { await loadRequiredData() } @@ -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 @@ -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) } } @@ -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" + ) } } diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Shipping Label/WooShipping Create Shipping Labels/WooShippingCreateLabelsViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Shipping Label/WooShipping Create Shipping Labels/WooShippingCreateLabelsViewModelTests.swift index 0757519ce04..402696c1689 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Shipping Label/WooShipping Create Shipping Labels/WooShippingCreateLabelsViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Shipping Label/WooShipping Create Shipping Labels/WooShippingCreateLabelsViewModelTests.swift @@ -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()) @@ -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 {