Skip to content

Commit 898af02

Browse files
authored
Merge pull request #9778 from woocommerce/issue/8962-product-bundle-ui
[Product Bundles] Display product bundle hierarchy in order details
2 parents dd180b4 + ff916a5 commit 898af02

File tree

12 files changed

+117
-22
lines changed

12 files changed

+117
-22
lines changed

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
13.8
44
-----
5+
- [Internal] Orders: Bundled products (within a product bundle) are now indented, to show their relationship to the parent bundle. [https://github.com/woocommerce/woocommerce-ios/pull/9778]
56
- [*] Add Products: A new view is display to celebrate when the first product is created in a store. [https://github.com/woocommerce/woocommerce-ios/pull/9790]
67
- [*] Product form: a share action is shown in the navigation bar if the product can be shared and no more than one action is displayed, in addition to the more menu > Share. [https://github.com/woocommerce/woocommerce-ios/pull/9789]
78

WooCommerce/Classes/Tools/AggregateData/AggregateDataHelper.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,11 @@ final class AggregateDataHelper {
142142

143143
return filtered.sorted()
144144
}
145+
146+
static func isChildItemWithParent(_ item: AggregateOrderItem, in items: [AggregateOrderItem]) -> Bool {
147+
guard let parentID = item.parent else {
148+
return false
149+
}
150+
return items.contains(where: { $0.itemID == parentID.description })
151+
}
145152
}

WooCommerce/Classes/ViewModels/Order Details/OrderDetailsDataSource.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -815,9 +815,11 @@ private extension OrderDetailsDataSource {
815815
}()
816816

817817
let addOns = filterAddOns(of: aggregateItem)
818+
let isChildWithParent = AggregateDataHelper.isChildItemWithParent(aggregateItem, in: aggregateOrderItems)
818819
let itemViewModel = ProductDetailsCellViewModel(aggregateItem: aggregateItem.copy(imageURL: imageURL),
819820
currency: order.currency,
820-
hasAddOns: addOns.isNotEmpty)
821+
hasAddOns: addOns.isNotEmpty,
822+
isChildWithParent: isChildWithParent)
821823

822824
cell.configure(item: itemViewModel, imageService: imageService)
823825
cell.accessibilityIdentifier = "single-product-cell"

WooCommerce/Classes/ViewModels/Order Details/Refunded Products/RefundedProductsDataSource.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,12 @@ private extension RefundedProductsDataSource {
113113
func configureRefundedProduct(_ cell: ProductDetailsTableViewCell, at indexPath: IndexPath) {
114114
let refundedProduct = refundedProducts[indexPath.row]
115115
let product = lookUpProduct(by: refundedProduct.productOrVariationID)
116+
let isChildWithParent = AggregateDataHelper.isChildItemWithParent(refundedProduct, in: refundedProducts)
116117
let refundedProductViewModel = ProductDetailsCellViewModel(aggregateItem: refundedProduct,
117-
currency: order.currency,
118-
product: product,
119-
hasAddOns: false)
118+
currency: order.currency,
119+
product: product,
120+
hasAddOns: false,
121+
isChildWithParent: isChildWithParent)
120122
let imageService = ServiceLocator.imageService
121123

122124
cell.selectionStyle = .default

WooCommerce/Classes/ViewModels/Order Details/Refunded Products/RefundedProductsViewModel.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ final class RefundedProductsViewModel {
1616
/// The datasource that will be used to render the Refunded Products screen.
1717
///
1818
private(set) lazy var dataSource: RefundedProductsDataSource = {
19-
let sortedItems = refundedProducts.sorted(by: { ($0.productID, $0.variationID) < ($1.productID, $1.variationID) })
20-
return RefundedProductsDataSource(order: order, refundedProducts: sortedItems)
19+
RefundedProductsDataSource(order: order, refundedProducts: refundedProducts)
2120
}()
2221

2322
/// Designated initializer.

WooCommerce/Classes/ViewModels/ProductDetailsCellViewModel.swift

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ struct ProductDetailsCellViewModel {
7979
///
8080
let hasAddOns: Bool
8181

82+
/// Whether the item is a child with a parent item.
83+
///
84+
let isChildProduct: Bool
85+
8286
// MARK: - Initializers
8387

8488
private init(currency: String,
@@ -90,7 +94,8 @@ struct ProductDetailsCellViewModel {
9094
price: NSDecimalNumber?,
9195
skuText: String?,
9296
attributes: [VariationAttributeViewModel],
93-
hasAddOns: Bool) {
97+
hasAddOns: Bool,
98+
isChildProduct: Bool) {
9499
self.imageURL = imageURL
95100
self.name = name
96101
let quantity = NumberFormatter.localizedString(from: positiveQuantity as NSDecimalNumber, number: .decimal)
@@ -117,6 +122,8 @@ struct ProductDetailsCellViewModel {
117122
let itemPrice = currencyFormatter.formatAmount(price, with: currency) ?? String()
118123
return Localization.subtitle(quantity: quantity, price: itemPrice, attributes: attributes)
119124
}()
125+
126+
self.isChildProduct = isChildProduct
120127
}
121128

122129
/// Order Item initializer
@@ -125,7 +132,8 @@ struct ProductDetailsCellViewModel {
125132
currency: String,
126133
formatter: CurrencyFormatter = CurrencyFormatter(currencySettings: ServiceLocator.currencySettings),
127134
product: Product? = nil,
128-
hasAddOns: Bool) {
135+
hasAddOns: Bool,
136+
isChildWithParent: Bool) {
129137
self.init(currency: currency,
130138
currencyFormatter: formatter,
131139
imageURL: product?.imageURL,
@@ -135,7 +143,8 @@ struct ProductDetailsCellViewModel {
135143
price: item.price,
136144
skuText: item.sku,
137145
attributes: item.attributes.map { VariationAttributeViewModel(orderItemAttribute: $0) },
138-
hasAddOns: hasAddOns)
146+
hasAddOns: hasAddOns,
147+
isChildProduct: isChildWithParent)
139148
}
140149

141150
/// Aggregate Order Item initializer
@@ -144,7 +153,8 @@ struct ProductDetailsCellViewModel {
144153
currency: String,
145154
formatter: CurrencyFormatter = CurrencyFormatter(currencySettings: ServiceLocator.currencySettings),
146155
product: Product? = nil,
147-
hasAddOns: Bool) {
156+
hasAddOns: Bool,
157+
isChildWithParent: Bool) {
148158
self.init(currency: currency,
149159
currencyFormatter: formatter,
150160
imageURL: aggregateItem.imageURL ?? product?.imageURL,
@@ -154,7 +164,8 @@ struct ProductDetailsCellViewModel {
154164
price: aggregateItem.price,
155165
skuText: aggregateItem.sku,
156166
attributes: aggregateItem.attributes.map { VariationAttributeViewModel(orderItemAttribute: $0) },
157-
hasAddOns: hasAddOns)
167+
hasAddOns: hasAddOns,
168+
isChildProduct: isChildWithParent)
158169
}
159170

160171
/// Refunded Order Item initializer
@@ -172,7 +183,8 @@ struct ProductDetailsCellViewModel {
172183
price: refundedItem.price,
173184
skuText: refundedItem.sku,
174185
attributes: [], // Attributes are not supported for a refund item yet.
175-
hasAddOns: false) // AddOns are not supported for a refund item yet.
186+
hasAddOns: false, // AddOns are not supported for a refund item yet.
187+
isChildProduct: false) // Parent/child relationships are not supported for a refund item.
176188
}
177189
}
178190

WooCommerce/Classes/ViewRelated/Orders/Order Details/Product List Section/Product Details/AggregatedProductListViewController.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,12 @@ extension AggregatedProductListViewController: UITableViewDataSource {
7373
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
7474
let item = itemAtIndexPath(indexPath)
7575
let product = viewModel.lookUpProduct(by: item.productOrVariationID)
76+
let isChildWithParent = AggregateDataHelper.isChildItemWithParent(item, in: items)
7677
let itemViewModel = ProductDetailsCellViewModel(aggregateItem: item,
7778
currency: viewModel.order.currency,
7879
product: product,
79-
hasAddOns: false)
80+
hasAddOns: false,
81+
isChildWithParent: isChildWithParent)
8082
let cell = tableView.dequeueReusableCell(PickListTableViewCell.self, for: indexPath)
8183
cell.selectionStyle = .default
8284
cell.configure(item: itemViewModel, imageService: imageService)

WooCommerce/Classes/ViewRelated/Orders/Order Details/Product List Section/ProductDetailsTableViewCell.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ final class ProductDetailsTableViewCell: UITableViewCell {
4040
///
4141
@IBOutlet private var viewAddOnsIndicator: UIImageView!
4242

43+
/// The leading constraint for the productImageView.
44+
///
45+
@IBOutlet var productImageLeadingConstraint: NSLayoutConstraint!
46+
4347
/// Assign this closure to be notified when the "viewAddOns" button us tapped
4448
///
4549
var onViewAddOnsTouchUp: (() -> Void)?
@@ -124,6 +128,16 @@ private extension ProductDetailsTableViewCell {
124128
func configureSelectionStyle() {
125129
selectionStyle = .none
126130
}
131+
132+
/// Adds padding between the leading margin and the product image if the product is a child product.
133+
///
134+
func configureChildProductPadding(isChildProduct: Bool) {
135+
if isChildProduct {
136+
productImageLeadingConstraint.constant = Constants.childProductLeadingPadding
137+
} else {
138+
productImageLeadingConstraint.constant = 0
139+
}
140+
}
127141
}
128142

129143

@@ -144,6 +158,7 @@ extension ProductDetailsTableViewCell {
144158
subtitleLabel.text = item.subtitle
145159
skuLabel.text = item.sku
146160
viewAddOnsStackView.isHidden = !item.hasAddOns
161+
configureChildProductPadding(isChildProduct: item.isChildProduct)
147162
}
148163
}
149164

@@ -152,4 +167,8 @@ private extension ProductDetailsTableViewCell {
152167
enum Localization {
153168
static let viewAddOns = NSLocalizedString("View Add-Ons", comment: "Title of the button on the order detail item to navigate to add-ons")
154169
}
170+
171+
enum Constants {
172+
static let childProductLeadingPadding: CGFloat = 44
173+
}
155174
}

WooCommerce/Classes/ViewRelated/Orders/Order Details/Product List Section/ProductDetailsTableViewCell.xib

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
33
<device id="retina4_7" orientation="portrait" appearance="light"/>
44
<dependencies>
55
<deployment identifier="iOS"/>
6-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
6+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
77
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
88
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
99
</dependencies>
@@ -107,6 +107,7 @@
107107
<connections>
108108
<outlet property="nameLabel" destination="14R-zK-tgC" id="LmK-XT-zbU"/>
109109
<outlet property="priceLabel" destination="pPE-SE-pY7" id="ir4-Ou-SuU"/>
110+
<outlet property="productImageLeadingConstraint" destination="WkU-sH-Aqw" id="tnC-rs-zLU"/>
110111
<outlet property="productImageView" destination="p6h-Fq-FjW" id="bIw-1F-5GW"/>
111112
<outlet property="skuLabel" destination="7JH-rt-n9v" id="U07-2n-W3b"/>
112113
<outlet property="subtitleLabel" destination="IBM-H2-zE2" id="acc-K8-vmg"/>

WooCommerce/Classes/ViewRelated/Orders/Order Details/Review Order/ReviewOrderViewModel.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,12 @@ extension ReviewOrderViewModel {
198198
func productDetailsCellViewModel(for item: AggregateOrderItem) -> ProductDetailsCellViewModel {
199199
let product = filterProduct(for: item)
200200
let addOns = filterAddons(for: item)
201-
return ProductDetailsCellViewModel(aggregateItem: item, currency: order.currency, product: product, hasAddOns: !addOns.isEmpty)
201+
let isChildWithParent = AggregateDataHelper.isChildItemWithParent(item, in: aggregateOrderItems)
202+
return ProductDetailsCellViewModel(aggregateItem: item,
203+
currency: order.currency,
204+
product: product,
205+
hasAddOns: !addOns.isEmpty,
206+
isChildWithParent: isChildWithParent)
202207
}
203208

204209
/// Get shipment tracking at specified index of order.

0 commit comments

Comments
 (0)