From 32559f8fcfbfea0d42c4688d4105b0a2b73ef771 Mon Sep 17 00:00:00 2001 From: Rachel McRoberts Date: Wed, 8 Jan 2025 14:42:52 +0000 Subject: [PATCH 1/2] Display origin address list in bottom sheet --- .../WooShippingCreateLabelsView.swift | 27 ++++++++++++++++--- .../WooShippingCreateLabelsViewModel.swift | 6 +++-- ...ooShippingCreateLabelsViewModelTests.swift | 18 +++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) 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 086715eaa76..96a531dc766 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 @@ -34,6 +34,9 @@ struct WooShippingCreateLabelsView: View { /// Whether the shipment details bottom sheet is expanded. @State private var isShipmentDetailsExpanded = false + /// Whether the origin address list sheet is presented. + @State private var isOriginAddressListPresented = false + var body: some View { NavigationStack { ScrollView { @@ -111,10 +114,20 @@ struct WooShippingCreateLabelsView: View { HStack(alignment: .firstTextBaseline, spacing: Layout.bottomSheetSpacing) { Text(Localization.BottomSheet.shipFrom) .trackSize(size: $shipmentDetailsShipFromSize) - Text(viewModel.originAddress) - .lineLimit(1) - .truncationMode(.tail) - .frame(maxWidth: .infinity, alignment: .leading) + Button { + isOriginAddressListPresented = true + } label: { + HStack { + Text(viewModel.originAddress) + .lineLimit(1) + .truncationMode(.tail) + .frame(maxWidth: .infinity, alignment: .leading) + Image(systemName: "ellipsis") + .frame(width: Layout.ellipsisWidth) + .bold() + } + } + .buttonStyle(TextButtonStyle()) } .padding(Layout.bottomSheetPadding) Divider() @@ -157,6 +170,11 @@ struct WooShippingCreateLabelsView: View { .padding([.bottom, .horizontal], Layout.bottomSheetPadding) } .ignoresSafeArea(edges: .horizontal) + .sheet(isPresented: $isOriginAddressListPresented) { + WooShippingOriginAddressListView(viewModel: viewModel.originAddresses) + .presentationDetents([.medium, .large]) + .presentationDragIndicator(.visible) + } } .shippingWeightUnit(viewModel.weightUnit) .shippingDimensionsUnit(viewModel.dimensionsUnit) @@ -271,6 +289,7 @@ private extension WooShippingCreateLabelsView { static let iconSize: CGFloat = 32 static let rowHeight: CGFloat = 32 static let chevronSize: CGFloat = 30 + static let ellipsisWidth: CGFloat = 22 static let bottomSheetSpacing: CGFloat = 16 static let bottomSheetPadding: CGFloat = 16 } 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 cee869be10d..4174f8d5c6b 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 @@ -9,7 +9,6 @@ final class WooShippingCreateLabelsViewModel: ObservableObject { private let currencyFormatter: CurrencyFormatter private let order: Order private let itemsDataSource: WooShippingItemsDataSource - private var originAddresses: [WooShippingOriginAddress] = [] private let destinationAddress: ShippingLabelAddress? private let stores: StoresManager private var subscriptions: Set = [] @@ -52,6 +51,9 @@ final class WooShippingCreateLabelsViewModel: ObservableObject { /// Selected shipping rate when creating a shipping label. @Published private var selectedRate: WooShippingSelectedRate? + /// View model for a list of origin addresses to ship from. + private(set) var originAddresses = WooShippingOriginAddressListViewModel(addresses: []) + /// Address to ship from (store address). @Published private var selectedOriginAddress: WooShippingOriginAddress? @@ -242,8 +244,8 @@ private extension WooShippingCreateLabelsViewModel { guard let self else { return } switch result { case .success(let addresses): - originAddresses = addresses selectedOriginAddress = addresses.first(where: \.defaultAddress) + originAddresses = WooShippingOriginAddressListViewModel(addresses: addresses, selectedAddressID: selectedOriginAddress?.id) case .failure(let error): DDLogError("⛔️ Error loading origin addresses for Woo Shipping labels: \(error)") } 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 5b1a080db86..fdc552a53f1 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 @@ -35,6 +35,24 @@ final class WooShippingCreateLabelsViewModelTests: XCTestCase { XCTAssertEqual(viewModel.shippingRates.count, 1) } + func test_origin_addresses_fetched_and_converted_to_originAddresses_view_model() { + // Given + let originAddress = WooShippingOriginAddress.fake().copy(id: "default", defaultAddress: true) + let stores = MockStoresManager(sessionManager: .testingInstance) + stores.whenReceivingAction(ofType: WooShippingAction.self) { action in + if case let .loadOriginAddresses(_, completion) = action { + completion(.success([originAddress])) + } + } + + // When + let viewModel = WooShippingCreateLabelsViewModel(order: Order.fake(), stores: stores) + + // Then + XCTAssertEqual(viewModel.originAddresses.addresses.count, 1) + XCTAssertEqual(viewModel.originAddresses.selectedAddressID, originAddress.id) + } + func test_default_origin_address_fetched_and_converted_to_formatted_originAddress() { // Given let originAddresses = [WooShippingOriginAddress.fake(), From a3611c5fc89799d1cf25fa5f8a02b9a0878929d4 Mon Sep 17 00:00:00 2001 From: Rachel McRoberts Date: Wed, 8 Jan 2025 15:25:50 +0000 Subject: [PATCH 2/2] Handle origin address selection --- ...WooShippingOriginAddressListViewModel.swift | 4 ++++ .../WooShippingCreateLabelsViewModel.swift | 6 +++++- ...ippingOriginAddressListViewModelTests.swift | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingAddresses/WooShippingOriginAddressListViewModel.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingAddresses/WooShippingOriginAddressListViewModel.swift index 8d3cfa62509..a21a53ddae7 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingAddresses/WooShippingOriginAddressListViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingAddresses/WooShippingOriginAddressListViewModel.swift @@ -5,6 +5,9 @@ final class WooShippingOriginAddressListViewModel: ObservableObject { let addresses: [WooShippingOriginAddress] @Published private(set) var selectedAddressID: String? + /// Closure (set externally) called when an address is selected. + var onSelect: ((WooShippingOriginAddress) -> Void)? + init(addresses: [WooShippingOriginAddress], selectedAddressID: String? = nil) { self.addresses = addresses @@ -22,6 +25,7 @@ final class WooShippingOriginAddressListViewModel: ObservableObject { return } selectedAddressID = address.id + onSelect?(address) } } 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 4174f8d5c6b..5f643ec3fc2 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 @@ -245,7 +245,11 @@ private extension WooShippingCreateLabelsViewModel { switch result { case .success(let addresses): selectedOriginAddress = addresses.first(where: \.defaultAddress) - originAddresses = WooShippingOriginAddressListViewModel(addresses: addresses, selectedAddressID: selectedOriginAddress?.id) + originAddresses = WooShippingOriginAddressListViewModel(addresses: addresses, + selectedAddressID: selectedOriginAddress?.id) + originAddresses.onSelect = { [weak self] selectedAddress in + self?.selectedOriginAddress = selectedAddress + } case .failure(let error): DDLogError("⛔️ Error loading origin addresses for Woo Shipping labels: \(error)") } diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Shipping Label/WooShipping Create Shipping Labels/WooShippingOriginAddressListViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Shipping Label/WooShipping Create Shipping Labels/WooShippingOriginAddressListViewModelTests.swift index 2af638fbf3d..08fd19d5036 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Shipping Label/WooShipping Create Shipping Labels/WooShippingOriginAddressListViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Shipping Label/WooShipping Create Shipping Labels/WooShippingOriginAddressListViewModelTests.swift @@ -56,4 +56,22 @@ final class WooShippingOriginAddressListViewModelTests: XCTestCase { XCTAssertEqual(viewModel.selectedAddressID, addressToSelect.id) } + func test_select_calls_onSelect_closure() { + // Given + let addressToSelect = WooShippingOriginAddress.fake().copy(id: "1") + let addresses = [addressToSelect, WooShippingOriginAddress.fake().copy(id: "2")] + let viewModel = WooShippingOriginAddressListViewModel(addresses: addresses, selectedAddressID: nil) + + // When + let selectedAddress = waitFor { promise in + viewModel.onSelect = { address in + promise(address) + } + viewModel.select(addressToSelect) + } + + // Then + XCTAssertEqual(selectedAddress, addressToSelect) + } + }