Skip to content

Commit 44b48ed

Browse files
authored
Merge branch 'trunk' into task/15065-failed-uploads-screen
2 parents e418582 + 8f37e46 commit 44b48ed

File tree

6 files changed

+87
-10
lines changed

6 files changed

+87
-10
lines changed

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [*] Product List: Display syncing animation on items with image upload in progress [https://github.com/woocommerce/woocommerce-ios/pull/15052]
1010
- [*] Background image upload: Fix issue showing uploaded images while saving is in progress [https://github.com/woocommerce/woocommerce-ios/pull/15107]
1111
- [*] Background image upload: Fix missing error notice in iPhones [https://github.com/woocommerce/woocommerce-ios/pull/15117]
12+
- [*] Background image upload: Show a notice when the user leaves product details while uploads are pending [https://github.com/woocommerce/woocommerce-ios/pull/15134]
1213
- [*] Filters applied in product selector no longer affect the main product list screen. [https://github.com/woocommerce/woocommerce-ios/pull/14764]
1314
- [**] Product Images: Update error handling [https://github.com/woocommerce/woocommerce-ios/pull/15105]
1415

WooCommerce/Classes/ServiceLocator/ProductImageUploader.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Combine
2+
import Foundation
23
import struct Yosemite.ProductImage
34
import enum Yosemite.ProductAction
45
import protocol Yosemite.StoresManager
@@ -78,6 +79,11 @@ protocol ProductImageUploaderProtocol {
7879
/// - key: identifiable information about the product.
7980
func startEmittingErrors(key: ProductImageUploaderKey)
8081

82+
/// Triggers a notice about background image upload for a product if needed.
83+
/// - Parameter key: identifiable information about the product.
84+
///
85+
func sendBackgroundUploadNoticeIfNeeded(key: ProductImageUploaderKey, using noticePresenter: NoticePresenter)
86+
8187
/// Determines whether there are unsaved changes on a product's images.
8288
/// If the product had any save request before, it checks whether the image statuses to save match the latest image statuses.
8389
/// Otherwise, it checks whether there is any pending upload or the image statuses match the given original image statuses.
@@ -166,6 +172,13 @@ final class ProductImageUploader: ProductImageUploaderProtocol {
166172
statusUpdatesExcludedProductKeys.remove(key)
167173
}
168174

175+
func sendBackgroundUploadNoticeIfNeeded(key: ProductImageUploaderKey, using noticePresenter: NoticePresenter) {
176+
if activeUploadsPublisher.contains(key) {
177+
let notice = Notice(title: Localization.backgroundUploadNoticeTitle)
178+
noticePresenter.enqueue(notice: notice)
179+
}
180+
}
181+
169182
func hasUnsavedChangesOnImages(key: ProductImageUploaderKey, originalImages: [ProductImage]) -> Bool {
170183
guard let handler = actionHandlersByProduct[key] else {
171184
return false
@@ -307,3 +320,11 @@ enum ProductImageUploaderError: Error {
307320
case failedSavingProductAfterImageUpload(error: Error)
308321
case failedUploadingImage(asset: ProductImageAssetType, error: Error)
309322
}
323+
324+
private enum Localization {
325+
static let backgroundUploadNoticeTitle = NSLocalizedString(
326+
"productImageUploader.backgroundUploadNotice.title",
327+
value: "Image uploading will continue in the background",
328+
comment: ""
329+
)
330+
}

WooCommerce/Classes/ViewRelated/Products/Edit Product/ProductFormViewController.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,7 @@ final class ProductFormViewController<ViewModel: ProductFormViewModelProtocol>:
195195
super.viewWillDisappear(animated)
196196

197197
view.endEditing(true)
198-
199-
if isBeingDismissedInAnyWay {
200-
productImageUploader.startEmittingErrors(key: .init(siteID: viewModel.productModel.siteID,
201-
productOrVariationID: productOrVariationID,
202-
isLocalID: !viewModel.productModel.existsRemotely))
203-
}
198+
prepareForBackgroundUploadsUponDismissal()
204199
}
205200

206201
override var shouldShowOfflineBanner: Bool {
@@ -1359,6 +1354,16 @@ private extension ProductFormViewController {
13591354
// MARK: - Navigation actions handling
13601355
//
13611356
private extension ProductFormViewController {
1357+
func prepareForBackgroundUploadsUponDismissal() {
1358+
guard isBeingDismissedInAnyWay else { return }
1359+
1360+
let key = ProductImageUploaderKey(siteID: viewModel.productModel.siteID,
1361+
productOrVariationID: productOrVariationID,
1362+
isLocalID: !viewModel.productModel.existsRemotely)
1363+
productImageUploader.startEmittingErrors(key: key)
1364+
productImageUploader.sendBackgroundUploadNoticeIfNeeded(key: key, using: ServiceLocator.noticePresenter)
1365+
}
1366+
13621367
func presentBackNavigationActionSheet(onDiscard: @escaping () -> Void = {}, onCancel: @escaping () -> Void = {}) {
13631368
let exitForm: () -> Void = {
13641369
presentationStyle.createExitForm(viewController: navigationController ?? self, completion: onDiscard)

WooCommerce/Classes/ViewRelated/Products/ProductsSplitViewCoordinator.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,7 @@ extension ProductsSplitViewCoordinator: UINavigationControllerDelegate {
231231
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
232232
if didNavigateFromTheLastSecondaryViewControllerToProductListInCollapsedMode(navigationController, didShow: viewController) {
233233
if let contentType = contentTypes.last, case let .productForm(product) = contentType, let product {
234-
ServiceLocator.productImageUploader.startEmittingErrors(
235-
key: .init(siteID: product.siteID,
236-
productOrVariationID: .product(id: product.productID),
237-
isLocalID: false))
234+
didDismissProductForm(product: product)
238235
}
239236
contentTypes = []
240237
secondaryNavigationController.viewControllers = []
@@ -275,6 +272,15 @@ private extension ProductsSplitViewCoordinator {
275272
return splitViewController.isCollapsed && navigationController == primaryNavigationController
276273
&& contentTypes.isNotEmpty && isNavigatingToProductList
277274
}
275+
276+
func didDismissProductForm(product: Product) {
277+
let uploader = ServiceLocator.productImageUploader
278+
let key = ProductImageUploaderKey(siteID: product.siteID,
279+
productOrVariationID: .product(id: product.productID),
280+
isLocalID: false)
281+
uploader.startEmittingErrors(key: key)
282+
uploader.sendBackgroundUploadNoticeIfNeeded(key: key, using: ServiceLocator.noticePresenter)
283+
}
278284
}
279285

280286
private extension ProductsSplitViewCoordinator {

WooCommerce/WooCommerceTests/Mocks/MockProductImageUploader.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ extension MockProductImageUploader: ProductImageUploaderProtocol {
6363
hasUnsavedChangesOnImages
6464
}
6565

66+
func sendBackgroundUploadNoticeIfNeeded(key: ProductImageUploaderKey, using noticePresenter: NoticePresenter) {
67+
// no-op
68+
}
69+
6670
func reset() {
6771
resetWasCalled = true
6872
}

WooCommerce/WooCommerceTests/ViewRelated/Products/Media/ProductImageUploaderTests.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,7 @@ final class ProductImageUploaderTests: XCTestCase {
611611
}
612612

613613
func test_product_is_removed_from_activeUploads_when_upload_is_cancelled() {
614+
// Given
614615
let stores = MockStoresManager(sessionManager: .testingInstance)
615616
let imageUploader = ProductImageUploader(stores: stores)
616617
let key = ProductImageUploaderKey(siteID: siteID,
@@ -642,6 +643,45 @@ final class ProductImageUploaderTests: XCTestCase {
642643
activeUploads == []
643644
}
644645
}
646+
647+
func test_background_upload_notice_is_sent_when_there_are_active_uploads() {
648+
// Given
649+
let stores = MockStoresManager(sessionManager: .testingInstance)
650+
let imageUploader = ProductImageUploader(stores: stores)
651+
let key = ProductImageUploaderKey(siteID: siteID,
652+
productOrVariationID: .product(id: productID),
653+
isLocalID: false)
654+
let actionHandler = imageUploader.actionHandler(key: key, originalStatuses: [])
655+
656+
let noticePresenter = MockNoticePresenter()
657+
var isNoticeTriggered = false
658+
noticePresenter.onNoticeQueued = { _ in
659+
isNoticeTriggered = true
660+
}
661+
662+
var activeUploads: [ProductImageUploaderKey] = []
663+
activeUploadsSubscription = imageUploader.activeUploads
664+
.sink { keys in
665+
activeUploads = keys
666+
}
667+
668+
// When
669+
imageUploader.sendBackgroundUploadNoticeIfNeeded(key: key, using: noticePresenter)
670+
671+
// Then
672+
XCTAssertFalse(isNoticeTriggered)
673+
674+
// When
675+
let asset = PHAsset()
676+
actionHandler.uploadMediaAssetToSiteMediaLibrary(asset: .phAsset(asset: asset))
677+
waitUntil {
678+
activeUploads == [key]
679+
}
680+
681+
// Then
682+
imageUploader.sendBackgroundUploadNoticeIfNeeded(key: key, using: noticePresenter)
683+
XCTAssertTrue(isNoticeTriggered)
684+
}
645685
}
646686

647687
extension ProductImageUploadErrorInfo: @retroactive Equatable {

0 commit comments

Comments
 (0)