Skip to content

Commit 46c5f23

Browse files
authored
Shipping Labels: Load settings and origin addresses synchronously only when data is not cached (#15935)
2 parents 14536d1 + b5c2853 commit 46c5f23

File tree

8 files changed

+326
-18
lines changed

8 files changed

+326
-18
lines changed

Modules/Sources/Storage/Model/MIGRATIONS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This file documents changes in the WCiOS Storage data model. Please explain any
1010
- Added `shipments` relationship to `Order` entity.
1111
- @itsmeichigo 2025-07-22
1212
- Added `WooShippingOriginAddress` entity.
13+
- Added attributes `lastOrderCompleted` and `addPaymentMethodURL` to `ShippingLabelAccountSettings` entity.
1314

1415
## Model 123 (Release 22.8.0.0)
1516
- @iamgabrielma 2025-06-30

Modules/Sources/Storage/Model/ShippingLabelAccountSettings+CoreDataProperties.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ extension ShippingLabelAccountSettings {
1212
@NSManaged public var canManagePayments: Bool
1313
@NSManaged public var isEmailReceiptsEnabled: Bool
1414
@NSManaged public var lastSelectedPackageID: String?
15+
@NSManaged public var lastOrderCompleted: Bool
1516
@NSManaged public var paperSize: String?
1617
@NSManaged public var selectedPaymentMethodID: Int64
1718
@NSManaged public var siteID: Int64
1819
@NSManaged public var storeOwnerDisplayName: String?
1920
@NSManaged public var storeOwnerUsername: String?
2021
@NSManaged public var storeOwnerWpcomEmail: String?
2122
@NSManaged public var storeOwnerWpcomUsername: String?
23+
@NSManaged public var addPaymentMethodURL: String?
2224
@NSManaged public var paymentMethods: Set<ShippingLabelPaymentMethod>?
2325

2426
}

Modules/Sources/Storage/Resources/WooCommerce.xcdatamodeld/Model 124.xcdatamodel/contents

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,9 +778,11 @@
778778
<relationship name="shipment" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="WooShippingShipment" inverseName="shippingLabel" inverseEntity="WooShippingShipment"/>
779779
</entity>
780780
<entity name="ShippingLabelAccountSettings" representedClassName="ShippingLabelAccountSettings" syncable="YES">
781+
<attribute name="addPaymentMethodURL" optional="YES" attributeType="String"/>
781782
<attribute name="canEditSettings" attributeType="Boolean" usesScalarValueType="YES"/>
782783
<attribute name="canManagePayments" attributeType="Boolean" usesScalarValueType="YES"/>
783784
<attribute name="isEmailReceiptsEnabled" attributeType="Boolean" usesScalarValueType="YES"/>
785+
<attribute name="lastOrderCompleted" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
784786
<attribute name="lastSelectedPackageID" attributeType="String" defaultValueString=""/>
785787
<attribute name="paperSize" attributeType="String"/>
786788
<attribute name="selectedPaymentMethodID" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>

Modules/Sources/Yosemite/Model/Storage/ShippingLabelAccountSettings+ReadOnlyConvertible.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ extension Storage.ShippingLabelAccountSettings: ReadOnlyConvertible {
1818
isEmailReceiptsEnabled = settings.isEmailReceiptsEnabled
1919
paperSize = settings.paperSize.rawValue
2020
lastSelectedPackageID = settings.lastSelectedPackageID
21+
lastOrderCompleted = settings.lastOrderCompleted
22+
addPaymentMethodURL = settings.addPaymentMethodURL?.absoluteString
2123
}
2224

2325
/// Returns a ReadOnly version of the receiver.
@@ -26,7 +28,6 @@ extension Storage.ShippingLabelAccountSettings: ReadOnlyConvertible {
2628
let paymentMethodItems = paymentMethods?.map { $0.toReadOnly() } ?? []
2729

2830
/// Since account settings are not persisted for the new shipping label flow,
29-
/// the conversion for the new properties `addPaymentMethodURL` & `lastOrderCompleted` is ignored.
3031
/// This avoids the complication of unnecessary Core Data migration for the new properties.
3132
return ShippingLabelAccountSettings(siteID: siteID,
3233
canManagePayments: canManagePayments,
@@ -40,7 +41,7 @@ extension Storage.ShippingLabelAccountSettings: ReadOnlyConvertible {
4041
isEmailReceiptsEnabled: isEmailReceiptsEnabled,
4142
paperSize: .init(rawValue: paperSize ?? ""),
4243
lastSelectedPackageID: lastSelectedPackageID ?? "",
43-
lastOrderCompleted: false,
44-
addPaymentMethodURL: nil)
44+
lastOrderCompleted: lastOrderCompleted,
45+
addPaymentMethodURL: URL(string: addPaymentMethodURL ?? ""))
4546
}
4647
}

Modules/Tests/StorageTests/CoreData/MigrationTests.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3518,6 +3518,57 @@ final class MigrationTests: XCTestCase {
35183518
let insertedAddress = try XCTUnwrap(targetContext.firstObject(ofType: WooShippingOriginAddress.self))
35193519
XCTAssertEqual(insertedAddress, address)
35203520
}
3521+
3522+
func test_migrating_from_123_to_124_adds_new_attributes_lastOrderCompleted_and_addPaymentMethodURL_to_ShippingLabelAccountSettings() throws {
3523+
// Given
3524+
let sourceContainer = try startPersistentContainer("Model 123")
3525+
let sourceContext = sourceContainer.viewContext
3526+
3527+
let object = sourceContext.insert(entityName: "ShippingLabelAccountSettings", properties: [
3528+
"siteID": 123,
3529+
"canEditSettings": false,
3530+
"canManagePayments": false,
3531+
"isEmailReceiptsEnabled": false,
3532+
"lastSelectedPackageID": "",
3533+
"paperSize": "",
3534+
"selectedPaymentMethodID": 0,
3535+
"storeOwnerDisplayName": "",
3536+
"storeOwnerUsername": "",
3537+
"storeOwnerWpcomEmail": "",
3538+
"storeOwnerWpcomUsername": ""
3539+
])
3540+
try sourceContext.save()
3541+
3542+
// `lastOrderCompleted` and `addPaymentMethodURL` should not be present in model 122
3543+
XCTAssertNil(object.entity.attributesByName["lastOrderCompleted"], "Precondition. Attribute does not exist.")
3544+
XCTAssertNil(object.entity.attributesByName["addPaymentMethodURL"], "Precondition. Attribute does not exist.")
3545+
3546+
// When
3547+
let targetContainer = try migrate(sourceContainer, to: "Model 124")
3548+
3549+
// Then
3550+
let targetContext = targetContainer.viewContext
3551+
let migratedObject = try XCTUnwrap(targetContext.first(entityName: "ShippingLabelAccountSettings"))
3552+
3553+
// `lastOrderCompleted` should be present in model 124
3554+
XCTAssertNotNil(migratedObject.entity.attributesByName["lastOrderCompleted"])
3555+
3556+
// `addPaymentMethodURL` value should default as nil in model 124
3557+
let value = migratedObject.value(forKey: "addPaymentMethodURL") as? String
3558+
XCTAssertNil(value)
3559+
3560+
// `lastOrderCompleted` must be settable
3561+
migratedObject.setValue(true, forKey: "lastOrderCompleted")
3562+
try targetContext.save()
3563+
let updatedValue = migratedObject.value(forKey: "lastOrderCompleted") as? Bool
3564+
XCTAssertEqual(updatedValue, true)
3565+
3566+
// `addPaymentMethodURL` must be settable
3567+
migratedObject.setValue("https://example.com", forKey: "addPaymentMethodURL")
3568+
try targetContext.save()
3569+
let urlValue = migratedObject.value(forKey: "addPaymentMethodURL") as? String
3570+
XCTAssertEqual(urlValue, "https://example.com")
3571+
}
35213572
}
35223573

35233574
// MARK: - Persistent Store Setup and Migrations

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- [*] Shipping Labels: Validate custom package dimensions [https://github.com/woocommerce/woocommerce-ios/pull/15925]
1515
- [*] Shipping Labels: Show UPS TOS modal in full length for better accessibility. [https://github.com/woocommerce/woocommerce-ios/pull/15926]
1616
- [*] Shipping Labels: Optimize data loading on purchase form [https://github.com/woocommerce/woocommerce-ios/pull/15919]
17+
- [*] Shipping Labels: Cache settings and origin addresses to improve loading experience for purchase form [https://github.com/woocommerce/woocommerce-ios/pull/15935]
1718
- [internal] Optimized assets for app size reduction [https://github.com/woocommerce/woocommerce-ios/pull/15881]
1819

1920
22.8

WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsViewModel.swift

Lines changed: 100 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,27 @@ final class WooShippingCreateLabelsViewModel: ObservableObject {
220220
return ResultsController<StorageWooShippingShipment>(storageManager: storageManager, matching: predicate, sortedBy: [descriptor])
221221
}()
222222

223+
/// Origin addresses Results Controller.
224+
///
225+
private lazy var originAddressResultsController: ResultsController<StorageWooShippingOriginAddress> = {
226+
let predicate = NSPredicate(format: "siteID = %ld", self.order.siteID)
227+
let descriptor = NSSortDescriptor(keyPath: \StorageWooShippingOriginAddress.id, ascending: true)
228+
229+
return ResultsController<StorageWooShippingOriginAddress>(storageManager: storageManager, matching: predicate, sortedBy: [descriptor])
230+
}()
231+
232+
/// Shipping Label Account Settings ResultsController
233+
///
234+
private lazy var accountSettingsResultsController: ResultsController<StorageShippingLabelAccountSettings> = {
235+
let predicate = NSPredicate(format: "siteID == %lld", order.siteID)
236+
return ResultsController<StorageShippingLabelAccountSettings>(
237+
storageManager: storageManager,
238+
matching: predicate,
239+
fetchLimit: 1,
240+
sortedBy: []
241+
)
242+
}()
243+
223244
/// Initialize the view model with or without an existing shipping label.
224245
init(order: Order,
225246
preselection: WooShippingCreateLabelSelection? = nil,
@@ -241,6 +262,8 @@ final class WooShippingCreateLabelsViewModel: ObservableObject {
241262
self.storageManager = storageManager
242263
self.analytics = analytics
243264
self.shippingSettingsService = shippingSettingsService
265+
self.weightUnit = shippingSettingsService.weightUnit ?? ""
266+
self.dimensionsUnit = shippingSettingsService.dimensionUnit ?? ""
244267
self.initialNoticeDelay = initialNoticeDelay
245268
self.isOrderCompleted = order.status == .completed
246269

@@ -267,6 +290,8 @@ final class WooShippingCreateLabelsViewModel: ObservableObject {
267290
observeViewStates()
268291
observePaymentMethod()
269292
configureShipmentResultsController()
293+
configureOriginAddressResultsController()
294+
configureAccountSettingsResultsController()
270295

271296
Task { @MainActor in
272297
await loadRequiredData()
@@ -291,16 +316,24 @@ final class WooShippingCreateLabelsViewModel: ObservableObject {
291316
func loadRequiredData() async {
292317
state = .loading
293318
await withTaskGroup(of: Void.self) { group in
319+
/// Only load store options synchronously if no settings have been saved in storage yet.
294320
if isMissingStoreSettings {
295321
group.addTask {
296322
await self.loadStoreOptions()
297323
}
324+
} else {
325+
/// load asynchronously to update the local storage and unblock UI
326+
stores.dispatch(WooShippingAction.loadAccountSettings(siteID: order.siteID, completion: { _ in }))
298327
}
299328

300-
if hasUnfulfilledShipments {
329+
/// Only load origin addresses synchronously if no addresses have been saved in storage yet.
330+
if hasUnfulfilledShipments, originAddress.isEmpty {
301331
group.addTask {
302332
await self.loadOriginAddresses()
303333
}
334+
} else if hasUnfulfilledShipments {
335+
/// load asynchronously to update the local storage and unblock UI
336+
stores.dispatch(WooShippingAction.loadOriginAddresses(siteID: order.siteID, completion: { _ in }))
304337
}
305338
}
306339

@@ -418,12 +451,7 @@ private extension WooShippingCreateLabelsViewModel {
418451
}
419452
weightUnit = settings?.storeOptions.weightUnit ?? shippingSettingsService.weightUnit ?? ""
420453
dimensionsUnit = settings?.storeOptions.dimensionUnit ?? shippingSettingsService.dimensionUnit ?? ""
421-
markOrderComplete = settings?.accountSettings.lastOrderCompleted ?? false
422-
423-
if let accountSettings = settings?.accountSettings {
424-
paymentMethodsViewModel = ShippingLabelPaymentMethodsViewModel(accountSettings: accountSettings)
425-
}
426-
setupPaymentMethod(accountSettings: settings?.accountSettings)
454+
updateAccountSettings(accountSettings: settings?.accountSettings)
427455
}
428456

429457
/// Syncs origin addresses to use for shipping label from remote.
@@ -442,12 +470,7 @@ private extension WooShippingCreateLabelsViewModel {
442470
}
443471
stores.dispatch(action)
444472
}
445-
selectedOriginAddress = addresses.first(where: \.defaultAddress)
446-
originAddresses = WooShippingOriginAddressListViewModel(addresses: addresses,
447-
selectedAddressID: selectedOriginAddress?.id)
448-
originAddresses.onSelect = { [weak self] selectedAddress in
449-
self?.selectedOriginAddress = selectedAddress
450-
}
473+
updateSelectedOriginAddress(addresses: addresses)
451474
}
452475

453476
/// Loads destination address of the order from remote.
@@ -673,6 +696,70 @@ private extension WooShippingCreateLabelsViewModel {
673696
DDLogError("⛔️ Unable to fetch shipments: \(error)")
674697
}
675698
}
699+
700+
func configureOriginAddressResultsController() {
701+
let updateSelectedAddress = { [weak self] in
702+
guard let self else { return }
703+
let addresses = originAddressResultsController.fetchedObjects
704+
updateSelectedOriginAddress(addresses: addresses)
705+
}
706+
originAddressResultsController.onDidChangeContent = {
707+
updateSelectedAddress()
708+
}
709+
710+
originAddressResultsController.onDidResetContent = {
711+
updateSelectedAddress()
712+
}
713+
714+
do {
715+
try originAddressResultsController.performFetch()
716+
updateSelectedAddress()
717+
} catch {
718+
DDLogError("⛔️ Unable to fetch origin addresses: \(error)")
719+
}
720+
}
721+
722+
func updateSelectedOriginAddress(addresses: [WooShippingOriginAddress]) {
723+
selectedOriginAddress = addresses.first(where: \.defaultAddress)
724+
originAddresses = WooShippingOriginAddressListViewModel(addresses: addresses,
725+
selectedAddressID: selectedOriginAddress?.id)
726+
originAddresses.onSelect = { [weak self] selectedAddress in
727+
self?.selectedOriginAddress = selectedAddress
728+
}
729+
}
730+
731+
/// Shipping Label Account Settings ResultsController monitoring
732+
///
733+
func configureAccountSettingsResultsController() {
734+
let updateSettings = { [weak self] in
735+
guard let self else { return }
736+
let settings = accountSettingsResultsController.fetchedObjects.first
737+
updateAccountSettings(accountSettings: settings)
738+
}
739+
accountSettingsResultsController.onDidChangeContent = {
740+
updateSettings()
741+
}
742+
743+
accountSettingsResultsController.onDidResetContent = {
744+
updateSettings()
745+
}
746+
747+
do {
748+
try accountSettingsResultsController.performFetch()
749+
updateSettings()
750+
} catch {
751+
DDLogError("⛔️ Unable to fetch woo shipping account settings: \(error)")
752+
}
753+
}
754+
755+
func updateAccountSettings(accountSettings: ShippingLabelAccountSettings?) {
756+
markOrderComplete = accountSettings?.lastOrderCompleted ?? false
757+
758+
if let accountSettings {
759+
paymentMethodsViewModel = ShippingLabelPaymentMethodsViewModel(accountSettings: accountSettings)
760+
}
761+
setupPaymentMethod(accountSettings: accountSettings)
762+
}
676763
}
677764

678765
private extension WooShippingCreateLabelsViewModel {

0 commit comments

Comments
 (0)