Skip to content

Commit 04022ac

Browse files
authored
Replace full objects for products in Blaze and Shipping Label flows (#15965)
2 parents 462f0f0 + b0623c3 commit 04022ac

File tree

15 files changed

+163
-107
lines changed

15 files changed

+163
-107
lines changed

Modules/Sources/Networking/Model/Product/Product.swift

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -795,13 +795,6 @@ public struct Product: Codable, GeneratedCopiable, Equatable, GeneratedFakeable
795795

796796
}
797797

798-
public extension Product {
799-
/// Default product URL {site_url}?post_type=product&p={product_id} works for all sites.
800-
func alternativePermalink(with siteURL: String) -> String {
801-
String(format: "%@?post_type=product&p=%d", siteURL, productID)
802-
}
803-
}
804-
805798
/// Defines all of the Product CodingKeys
806799
///
807800
private extension Product {

Modules/Tests/NetworkingTests/Mapper/ProductMapperTests.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ final class ProductMapperTests: XCTestCase {
2929
XCTAssertEqual(product.name, "Book the Green Room")
3030
XCTAssertEqual(product.slug, "book-the-green-room")
3131
XCTAssertEqual(product.permalink, "https://example.com/product/book-the-green-room/")
32-
XCTAssertEqual(product.alternativePermalink(with: "https://example.com"),
33-
"https://example.com?post_type=product&p=\(dummyProductID)")
3432

3533
let dateCreated = DateFormatter.Defaults.dateTimeFormatter.date(from: "2019-02-19T17:33:31")
3634
let dateModified = DateFormatter.Defaults.dateTimeFormatter.date(from: "2019-02-19T17:48:01")

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- [*] Fix initialization of authenticator to avoid crashes during login [https://github.com/woocommerce/woocommerce-ios/pull/15953]
88
- [*] Shipping Labels: Made HS tariff number field required in customs form for EU destinations [https://github.com/woocommerce/woocommerce-ios/pull/15946]
99
- [*] Order Details: Attempt to improve performance by using a simplified version of product objects. [https://github.com/woocommerce/woocommerce-ios/pull/15959]
10+
- [*] Use simple product objects in Shipping Labels and Blaze flows. [https://github.com/woocommerce/woocommerce-ios/pull/15965]
1011
- [*] Product List: Load list with simplified product objects to improve performance. [https://teamkiwip2.wordpress.com/2025/08/01/hack-week-improving-performance-when-loading-cached-products/]
1112
- [*] Order Details > Edit Shipping/Billing Address: Added map-based address lookup support for iOS 17+. [https://github.com/woocommerce/woocommerce-ios/pull/15964]
1213
- [*] Order Creation: Prevent subscription products to be added to an order [https://github.com/woocommerce/woocommerce-ios/pull/15960]

WooCommerce/Classes/Model/ShippingLabelPackageItem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ extension ShippingLabelPackageItem {
5050
self.imageURL = copy.imageURL
5151
}
5252

53-
init?(orderItem: OrderItem, products: [Product], productVariations: [ProductVariation]) {
53+
init?(orderItem: OrderItem, products: [ShippingLabelProduct], productVariations: [ProductVariation]) {
5454
self.name = orderItem.name
5555
self.orderItemID = orderItem.itemID
5656
self.quantity = orderItem.quantity
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Foundation
2+
import Yosemite
3+
4+
/// Represents a Product Entity with basic details to display in the product section of shipping label creation form.
5+
///
6+
struct ShippingLabelProduct: Equatable {
7+
let productID: Int64
8+
let virtual: Bool
9+
let weight: String?
10+
let dimensions: ProductDimensions
11+
12+
let imageURL: URL?
13+
14+
init(storageProduct: StorageProduct) {
15+
self.productID = storageProduct.productID
16+
self.virtual = storageProduct.virtual
17+
self.weight = storageProduct.weight
18+
self.dimensions = {
19+
guard let dimensions = storageProduct.dimensions else {
20+
return ProductDimensions(length: "", width: "", height: "")
21+
}
22+
23+
return ProductDimensions(length: dimensions.length, width: dimensions.width, height: dimensions.height)
24+
}()
25+
self.imageURL = storageProduct.imagesArray.first?.toReadOnly().imageURL
26+
}
27+
}

WooCommerce/Classes/ViewRelated/Blaze/CampaignCreation/BlazeCampaignCreationFormViewModel.swift

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,7 @@ final class BlazeCampaignCreationFormViewModel: ObservableObject {
203203
@Published private(set) var isUsingAISuggestions: Bool = false
204204

205205
private let storage: StorageManagerType
206-
private var product: Product? {
207-
guard let product = productsResultsController.fetchedObjects.first else {
208-
assertionFailure("Unable to fetch product with ID: \(productID)")
209-
return nil
210-
}
211-
return product
212-
}
206+
private var product: BlazeCampaignProduct?
213207

214208
@Published private(set) var error: BlazeCampaignCreationError?
215209
private var suggestions: [BlazeAISuggestion] = []
@@ -232,9 +226,14 @@ final class BlazeCampaignCreationFormViewModel: ObservableObject {
232226

233227
/// ResultController to get the product for the given product ID
234228
///
235-
private lazy var productsResultsController: ResultsController<StorageProduct> = {
229+
private lazy var productsResultsController: GenericResultsController<StorageProduct, BlazeCampaignProduct> = {
236230
let predicate = \StorageProduct.siteID == siteID && \StorageProduct.productID == productID
237-
let controller = ResultsController<StorageProduct>(storageManager: storage, matching: predicate, sortedBy: [])
231+
let controller = GenericResultsController<StorageProduct, BlazeCampaignProduct>(
232+
storageManager: storage,
233+
matching: predicate,
234+
sortedBy: [],
235+
transformer: { BlazeCampaignProduct(storageProduct: $0) }
236+
)
238237
do {
239238
try controller.performFetch()
240239
} catch {
@@ -306,6 +305,8 @@ final class BlazeCampaignCreationFormViewModel: ObservableObject {
306305
// sets isEvergreen = true by default if evergreen campaigns are supported
307306
self.isEvergreen = featureFlagService.isFeatureFlagEnabled(.blazeEvergreenCampaigns)
308307

308+
product = productsResultsController.fetchedObjects.first
309+
309310
initializeCampaignObjective()
310311
updateBudgetDetails()
311312
updateTargetLanguagesText()
@@ -491,7 +492,7 @@ extension BlazeCampaignCreationFormViewModel {
491492
private extension BlazeCampaignCreationFormViewModel {
492493
@MainActor
493494
func loadProductImage() async -> MediaPickerImage? {
494-
guard let firstImage = product?.images.first,
495+
guard let firstImage = product?.firstImage,
495496
let image = try? await productImageLoader.requestImage(productImage: firstImage) else {
496497
return nil
497498
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Foundation
2+
import Yosemite
3+
4+
/// Represents a Product Entity with basic details to display in the Blaze flow.
5+
///
6+
struct BlazeCampaignProduct: Equatable {
7+
let productID: Int64
8+
let name: String
9+
let permalink: String
10+
let fullDescription: String?
11+
let shortDescription: String?
12+
13+
let firstImage: ProductImage?
14+
15+
func alternativePermalink(with siteURL: String) -> String {
16+
String(format: "%@?post_type=product&p=%d", siteURL, productID)
17+
}
18+
19+
init(storageProduct: StorageProduct) {
20+
self.productID = storageProduct.productID
21+
self.name = storageProduct.name
22+
self.permalink = storageProduct.permalink
23+
self.fullDescription = storageProduct.fullDescription
24+
self.shortDescription = storageProduct.briefDescription
25+
self.firstImage = storageProduct.imagesArray.first?.toReadOnly()
26+
}
27+
}

WooCommerce/Classes/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardView.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import SwiftUI
2-
import struct Yosemite.Product
32
import Kingfisher
43
import struct Yosemite.DashboardCard
54

@@ -294,15 +293,15 @@ private struct ProductInfoView: View {
294293
/// Scale of the view based on accessibility changes
295294
@ScaledMetric private var scale: CGFloat = 1.0
296295

297-
private let product: Product
296+
private let product: BlazeCampaignProduct
298297

299-
init(product: Product) {
298+
init(product: BlazeCampaignProduct) {
300299
self.product = product
301300
}
302301

303302
var body: some View {
304303
HStack(alignment: .center, spacing: Layout.contentSpacing) {
305-
KFImage(product.imageURL)
304+
KFImage(product.firstImage?.imageURL)
306305
.placeholder {
307306
Image(uiImage: .productPlaceholderImage)
308307
}

WooCommerce/Classes/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModel.swift

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ final class BlazeCampaignDashboardViewModel: ObservableObject {
1414
/// Shows info about the latest Blaze campaign
1515
case showCampaign(campaign: BlazeCampaignListItem)
1616
/// Shows info about the latest published Product
17-
case showProduct(product: Product)
17+
case showProduct(product: BlazeCampaignProduct)
1818
/// When there is no campaign or published product
1919
case empty
2020
}
@@ -90,19 +90,20 @@ final class BlazeCampaignDashboardViewModel: ObservableObject {
9090
}()
9191

9292
/// Product ResultsController.
93-
private lazy var productResultsController: ResultsController<StorageProduct> = {
93+
private lazy var productResultsController: GenericResultsController<StorageProduct, BlazeCampaignProduct> = {
9494
let predicate = NSPredicate(format: "siteID == %lld AND statusKey ==[c] %@",
9595
siteID,
9696
ProductStatus.published.rawValue)
97-
return ResultsController<StorageProduct>(storageManager: storageManager,
98-
matching: predicate,
99-
fetchLimit: 1,
100-
sortOrder: .dateDescending)
97+
return GenericResultsController<StorageProduct, BlazeCampaignProduct>(
98+
storageManager: storageManager,
99+
matching: predicate,
100+
fetchLimit: 1,
101+
sortedBy: [NSSortDescriptor(key: "date", ascending: false)],
102+
transformer: { BlazeCampaignProduct(storageProduct: $0) }
103+
)
101104
}()
102105

103-
var latestPublishedProduct: Product? {
104-
productResultsController.fetchedObjects.first
105-
}
106+
private(set) var latestPublishedProduct: BlazeCampaignProduct?
106107

107108
private var subscriptions: Set<AnyCancellable> = []
108109

@@ -314,9 +315,14 @@ private extension BlazeCampaignDashboardViewModel {
314315
self?.updateResults()
315316
}
316317

318+
let productTransformer: (StorageProduct) -> BlazeCampaignProduct = {
319+
BlazeCampaignProduct(storageProduct: $0)
320+
}
317321
productResultsController.onDidChangeContent = { [weak self] in
318-
self?.updateAvailability()
319-
self?.updateResults()
322+
guard let self else { return }
323+
latestPublishedProduct = productResultsController.fetchedObjects.first
324+
updateAvailability()
325+
updateResults()
320326
}
321327
productResultsController.onDidResetContent = { [weak self] in
322328
self?.updateAvailability()
@@ -326,6 +332,7 @@ private extension BlazeCampaignDashboardViewModel {
326332
do {
327333
try blazeCampaignResultsController.performFetch()
328334
try productResultsController.performFetch()
335+
latestPublishedProduct = productResultsController.fetchedObjects.first
329336
updateResults()
330337
} catch {
331338
ServiceLocator.crashLogging.logError(error)

WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/Create Shipping Label Form/Package Details/Multi-package/ShippingLabelPackagesFormViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ final class ShippingLabelPackagesFormViewModel: ObservableObject {
6161

6262
/// Products contained inside the Order and fetched from Core Data
6363
///
64-
@Published private var products: [Product] = []
64+
@Published private var products: [ShippingLabelProduct] = []
6565

6666
/// ProductVariations contained inside the Order and fetched from Core Data
6767
///

0 commit comments

Comments
 (0)