diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift index 10e1494c7a8..f9ea9518029 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift @@ -123,6 +123,7 @@ final class DashboardViewController: UIViewController { observeStatsVersionForDashboardUIUpdates() observeAnnouncements() observeShowWebViewSheet() + observeAddProductTrigger() viewModel.syncAnnouncements(for: siteID) Task { @MainActor in await reloadDashboardUIStatsVersion(forced: true) @@ -303,6 +304,28 @@ private extension DashboardViewController { present(hostingController, animated: true, completion: nil) } + /// Subscribes to the trigger to start the Add Product flow for products onboarding + /// + private func observeAddProductTrigger() { + viewModel.addProductTrigger.sink { [weak self] _ in + self?.startAddProductFlow() + } + .store(in: &subscriptions) + } + + /// Starts the Add Product flow (without switching tabs) + /// + private func startAddProductFlow() { + guard let announcementView, let navigationController else { return } + let coordinator = AddProductCoordinator(siteID: siteID, sourceView: announcementView, sourceNavigationController: navigationController) + coordinator.onProductCreated = { [weak self] in + guard let self else { return } + self.viewModel.announcementViewModel = nil // Remove the products onboarding banner + self.viewModel.syncAnnouncements(for: self.siteID) + } + coordinator.start() + } + // This is used so we have a specific type for the view while applying modifiers. struct AnnouncementCardWrapper: View { let cardView: FeatureAnnouncementCardView diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift index 6654c95092e..f0827dc8264 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift @@ -1,4 +1,5 @@ import Yosemite +import Combine import enum Networking.DotcomError import enum Storage.StatsVersion import protocol Experiments.FeatureFlagService @@ -13,6 +14,10 @@ final class DashboardViewModel { @Published private(set) var showWebViewSheet: WebViewSheetViewModel? = nil + /// Trigger to start the Add Product flow + /// + let addProductTrigger = PassthroughSubject() + private let stores: StoresManager private let featureFlagService: FeatureFlagService private let analytics: Analytics @@ -154,8 +159,7 @@ final class DashboardViewModel { guard let self else { return } if case let .success(isVisible) = result, isVisible { let viewModel = ProductsOnboardingAnnouncementCardViewModel(onCTATapped: { [weak self] in - self?.announcementViewModel = nil // Dismiss announcement - MainTabBarController.presentAddProductFlow() + self?.addProductTrigger.send() }) self.announcementViewModel = viewModel } diff --git a/WooCommerce/Classes/ViewRelated/MainTabBarController.swift b/WooCommerce/Classes/ViewRelated/MainTabBarController.swift index ed25658deb2..0ffb5638ea1 100644 --- a/WooCommerce/Classes/ViewRelated/MainTabBarController.swift +++ b/WooCommerce/Classes/ViewRelated/MainTabBarController.swift @@ -414,19 +414,6 @@ extension MainTabBarController { } } - static func presentAddProductFlow() { - navigateTo(.products) - - guard let productsViewController: ProductsViewController = childViewController() else { - return - } - - // We give some time for the products tab transition to finish, so the add product button is present to start the flow - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { - productsViewController.addProduct() - } - } - static func presentPayments() { switchToHubMenuTab() diff --git a/WooCommerce/Classes/ViewRelated/Products/Add Product/AddProductCoordinator.swift b/WooCommerce/Classes/ViewRelated/Products/Add Product/AddProductCoordinator.swift index 7abfb397529..46337b05e57 100644 --- a/WooCommerce/Classes/ViewRelated/Products/Add Product/AddProductCoordinator.swift +++ b/WooCommerce/Classes/ViewRelated/Products/Add Product/AddProductCoordinator.swift @@ -30,6 +30,10 @@ final class AddProductCoordinator: Coordinator { return controller }() + /// Assign this closure to be notified when a new product is saved remotely + /// + var onProductCreated: () -> Void = {} + init(siteID: Int64, sourceBarButtonItem: UIBarButtonItem, sourceNavigationController: UINavigationController, @@ -211,6 +215,7 @@ private extension AddProductCoordinator { let viewModel = ProductFormViewModel(product: model, formType: .add, productImageActionHandler: productImageActionHandler) + viewModel.onProductCreated = onProductCreated let viewController = ProductFormViewController(viewModel: viewModel, eventLogger: ProductFormEventLogger(), productImageActionHandler: productImageActionHandler, diff --git a/WooCommerce/Classes/ViewRelated/Products/Edit Product/ProductFormViewModel.swift b/WooCommerce/Classes/ViewRelated/Products/Edit Product/ProductFormViewModel.swift index 80b74b51a82..bc92240347d 100644 --- a/WooCommerce/Classes/ViewRelated/Products/Edit Product/ProductFormViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Products/Edit Product/ProductFormViewModel.swift @@ -181,6 +181,10 @@ final class ProductFormViewModel: ProductFormViewModelProtocol { private let featureFlagService: FeatureFlagService + /// Assign this closure to be notified when a new product is saved remotely + /// + var onProductCreated: () -> Void = {} + init(product: EditableProductModel, formType: ProductFormType, productImageActionHandler: ProductImageActionHandler, @@ -432,7 +436,7 @@ extension ProductFormViewModel { let productWithStatusUpdated = product.product.copy(statusKey: status.rawValue) return EditableProductModel(product: productWithStatusUpdated) }() - let remoteActionUseCase = ProductFormRemoteActionUseCase() + let remoteActionUseCase = ProductFormRemoteActionUseCase(stores: stores) switch formType { case .add: let productIDBeforeSave = productModel.productID @@ -449,6 +453,7 @@ extension ProductFormViewModel { onCompletion(.success(data.product)) self.replaceProductID(productIDBeforeSave: productIDBeforeSave) self.saveProductImagesWhenNoneIsPendingUploadAnymore() + self.onProductCreated() } } case .edit: diff --git a/WooCommerce/Classes/ViewRelated/Products/ProductsViewController.swift b/WooCommerce/Classes/ViewRelated/Products/ProductsViewController.swift index 3350f7df22b..d3726d9c08a 100644 --- a/WooCommerce/Classes/ViewRelated/Products/ProductsViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Products/ProductsViewController.swift @@ -220,16 +220,6 @@ final class ProductsViewController: UIViewController, GhostableViewController { } } -// MARK: - Public API -// -extension ProductsViewController { - /// Adds a new product using the "Add Product" navigation bar button as the source - /// - func addProduct() { - addProduct(addProductButton) - } -} - // MARK: - Navigation Bar Actions // private extension ProductsViewController { diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Products/Edit Product/ProductFormViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Products/Edit Product/ProductFormViewModelTests.swift index b3c1a003d86..3d4f1093330 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Products/Edit Product/ProductFormViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Products/Edit Product/ProductFormViewModelTests.swift @@ -579,6 +579,30 @@ final class ProductFormViewModelTests: XCTestCase { let hasLinkedProducts = try XCTUnwrap(analyticsProvider.receivedProperties.first?["has_linked_products"] as? Bool) XCTAssertTrue(hasLinkedProducts) } + + func test_onProductCreated_called_when_new_product_saved_remotely() { + // Given + var isCallbackCalled = false + let stores = MockStoresManager(sessionManager: .testingInstance) + let viewModel = createViewModel(product: Product.fake(), formType: .add, stores: stores) + viewModel.onProductCreated = { + isCallbackCalled = true + } + + // When + stores.whenReceivingAction(ofType: ProductAction.self) { action in + switch action { + case let .addProduct(product, onCompletion): + onCompletion(.success(product)) + default: + XCTFail("Received unsupported action: \(action)") + } + } + viewModel.saveProductRemotely(status: .draft) { _ in } + + // Then + XCTAssertTrue(isCallbackCalled) + } } private extension ProductFormViewModelTests {