diff --git a/WooCommerce/Classes/POS/POSItemsService.swift b/WooCommerce/Classes/POS/POSItemsService.swift new file mode 100644 index 00000000000..46ec7f9b226 --- /dev/null +++ b/WooCommerce/Classes/POS/POSItemsService.swift @@ -0,0 +1,30 @@ +import Foundation +import protocol Yosemite.POSItemProvider +import protocol Yosemite.POSItem + +struct POSItemsService { + + private let itemProvider: POSItemProvider + + init(itemProvider: POSItemProvider) { + self.itemProvider = itemProvider + } + + @MainActor + func fetchItems(pageNumber: Int, + currentItems: [any POSDisplayableItem]) async throws -> [any POSDisplayableItem] { + var newItems = try await itemProvider.providePointOfSaleItems(pageNumber: pageNumber) + if pageNumber == 1 { + newItems.insert(POSDiscount(), at: 0) + } + + let uniqueNewItems = newItems + .filter { newItem in + !currentItems.contains(where: { $0.id == newItem.itemID }) + } + .compactMap(createPOSDisplayableItem(for:)) + + let allItems = currentItems + uniqueNewItems + return allItems + } +} diff --git a/WooCommerce/Classes/POS/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/PointOfSaleAggregateModel.swift index 79e28d1c503..cbd5452049a 100644 --- a/WooCommerce/Classes/POS/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/PointOfSaleAggregateModel.swift @@ -3,7 +3,6 @@ import Combine import protocol Yosemite.POSOrderServiceProtocol import protocol Yosemite.POSItem -import protocol Yosemite.POSItemProvider import struct Yosemite.POSProduct import struct Yosemite.Order import struct Yosemite.OrderItem @@ -19,28 +18,23 @@ final class PointOfSaleAggregateModel: ObservableObject { } @Published private(set) var orderStage: OrderStage = .building - private var allItems: [any POSDisplayableItem] = [] @Published private(set) var cart: [CartItem] = [] @Published private(set) var orderState: PointOfSaleOrderState = .idle @Published private(set) var order: Order? = nil @Published private(set) var connectionStatus: CardPresentPaymentReaderConnectionStatus = .disconnected @Published private(set) var paymentState: PointOfSalePaymentState = .acceptingCard - @Published private(set) var itemListState: PointOfSaleItemListState = .initialLoading private let orderService: POSOrderServiceProtocol let cardPresentPaymentService: CardPresentPaymentFacade - private let itemProvider: POSItemProvider private let analytics: Analytics private var cancellables: Set = [] private var startPaymentOnReaderConnection: AnyCancellable? private var cardReaderDisconnection: AnyCancellable? - init(itemProvider: POSItemProvider, - cardPresentPaymentService: CardPresentPaymentFacade, + init(cardPresentPaymentService: CardPresentPaymentFacade, orderService: POSOrderServiceProtocol, analytics: Analytics) { - self.itemProvider = itemProvider self.cardPresentPaymentService = cardPresentPaymentService self.orderService = orderService self.analytics = analytics @@ -58,57 +52,6 @@ final class PointOfSaleAggregateModel: ObservableObject { .store(in: &cancellables) } - @MainActor - func loadInitialItems() async { - do { - itemListState = .initialLoading - try await fetchItems(pageNumber: 1) - } catch { - itemListState = .error(PointOfSaleErrorState.errorOnLoadingProducts()) - } - } - - @MainActor - func loadItems(pageNumber: Int) async { - do { - itemListState = .loading(allItems) - try await fetchItems(pageNumber: pageNumber) - } catch { - itemListState = .error(PointOfSaleErrorState.errorOnLoadingProducts()) - } - } - - @MainActor - private func fetchItems(pageNumber: Int) async throws { - var newItems = try await itemProvider.providePointOfSaleItems(pageNumber: pageNumber) - if pageNumber == 1 { - newItems.insert(POSDiscount(), at: 0) - } - let uniqueNewItems = newItems - .filter { newItem in - !allItems.contains(where: { $0.id == newItem.itemID }) - } - .compactMap(createPOSDisplayableItem(for:)) - - allItems.append(contentsOf: uniqueNewItems) - - if allItems.count == 0 { - itemListState = .empty - } else { - itemListState = .loaded(allItems) - } - } - - @MainActor - func reloadItems() async { - removeAllItems() - await loadItems(pageNumber: 1) - } - - func removeAllItems() { - allItems.removeAll() - } - func startNewOrder() { clearOrder() removeAllItemsFromCart() @@ -137,7 +80,7 @@ final class PointOfSaleAggregateModel: ObservableObject { @MainActor func submitCart() async { orderStage = .finalizing - await startSyncingOrder(with: cart, allItems: allItems.map { $0.item }) + await startSyncingOrder(with: cart, allItems: cart.map { $0.item }) } private func startSyncingOrder(with cartItems: [CartItem], allItems: [POSItem]) async { diff --git a/WooCommerce/Classes/POS/Presentation/CardReaderConnection/CardReaderConnectionStatusView.swift b/WooCommerce/Classes/POS/Presentation/CardReaderConnection/CardReaderConnectionStatusView.swift index f6e51c8b405..303308569c7 100644 --- a/WooCommerce/Classes/POS/Presentation/CardReaderConnection/CardReaderConnectionStatusView.swift +++ b/WooCommerce/Classes/POS/Presentation/CardReaderConnection/CardReaderConnectionStatusView.swift @@ -162,7 +162,6 @@ private extension CardReaderConnectionStatusView { import class WooFoundation.MockAnalyticsPreview #Preview { let posModel = PointOfSaleAggregateModel( - itemProvider: POSItemProviderPreview(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderService: POSOrderPreviewService(), analytics: MockAnalyticsPreview()) diff --git a/WooCommerce/Classes/POS/Presentation/CartView.swift b/WooCommerce/Classes/POS/Presentation/CartView.swift index a47f7292bf5..6b5be7978f7 100644 --- a/WooCommerce/Classes/POS/Presentation/CartView.swift +++ b/WooCommerce/Classes/POS/Presentation/CartView.swift @@ -270,7 +270,6 @@ import class WooFoundation.MockAnalyticsProviderPreview // TODO: // Simplify this by mocking `CartViewModel` let posModel = PointOfSaleAggregateModel( - itemProvider: POSItemProviderPreview(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderService: POSOrderPreviewService(), analytics: MockAnalyticsPreview()) @@ -279,7 +278,6 @@ import class WooFoundation.MockAnalyticsProviderPreview currencyFormatter: .init(currencySettings: .init())) let cartViewModel = CartViewModel(analytics: MockAnalyticsPreview(), posModel: posModel) - let itemsListViewModel = ItemListViewModel(posModel: posModel) let dashboardViewModel = PointOfSaleDashboardViewModel( posModel: posModel, connectivityObserver: POSConnectivityObserverPreview()) diff --git a/WooCommerce/Classes/POS/Presentation/ItemListView.swift b/WooCommerce/Classes/POS/Presentation/ItemListView.swift index 7a747a24334..35d0b24e769 100644 --- a/WooCommerce/Classes/POS/Presentation/ItemListView.swift +++ b/WooCommerce/Classes/POS/Presentation/ItemListView.swift @@ -2,24 +2,24 @@ import SwiftUI import protocol Yosemite.POSItem struct ItemListView: View { - @ObservedObject var viewModel: ItemListViewModel - @ObservedObject var posModel: PointOfSaleAggregateModel @Environment(\.floatingControlAreaSize) var floatingControlAreaSize: CGSize @Environment(\.dynamicTypeSize) private var dynamicTypeSize - init(viewModel: ItemListViewModel, - posModel: PointOfSaleAggregateModel) { - self.viewModel = viewModel - self.posModel = posModel - } + @Binding var itemListState: PointOfSaleItemListState + + var loadNextItems: () async -> Void + var reloadItems: () async -> Void + + @State private var isHeaderBannerDismissed: Bool = UserDefaults.standard.bool(forKey: BannerState.isSimpleProductsOnlyBannerDismissedKey) + @State private var showSimpleProductsModal: Bool = false var body: some View { VStack { headerView - .posModal(isPresented: $viewModel.showSimpleProductsModal) { - SimpleProductsOnlyInformation(isPresented: $viewModel.showSimpleProductsModal) + .posModal(isPresented: $showSimpleProductsModal) { + SimpleProductsOnlyInformation(isPresented: $showSimpleProductsModal) } - switch posModel.itemListState { + switch itemListState { case .initialLoading, .empty, .error: // These cases are handled directly in the dashboard, we do not render // a specific view within the ItemListView to handle them @@ -29,7 +29,7 @@ struct ItemListView: View { } } .refreshable { - await posModel.reloadItems() + await reloadItems() } .background(Color.posPrimaryBackground) .accessibilityElement(children: .contain) @@ -44,10 +44,10 @@ private extension ItemListView { VStack { HStack { POSHeaderTitleView() - if !viewModel.shouldShowHeaderBanner { + if isHeaderBannerDismissed { Spacer() Button(action: { - viewModel.simpleProductsInfoButtonTapped() + showSimpleProductsModal = true }, label: { Image(systemName: "info.circle") .font(.posTitleRegular) @@ -56,7 +56,7 @@ private extension ItemListView { .padding(.trailing, Constants.infoIconPadding) } } - if !dynamicTypeSize.isAccessibilitySize, viewModel.shouldShowHeaderBanner { + if !dynamicTypeSize.isAccessibilitySize, !isHeaderBannerDismissed { bannerCardView .padding(.horizontal, Constants.bannerCardPadding) } @@ -92,7 +92,8 @@ private extension ItemListView { .padding(.vertical, Constants.bannerVerticalPadding) VStack { Button(action: { - viewModel.dismissBanner() + isHeaderBannerDismissed = true + UserDefaults.standard.set(isHeaderBannerDismissed, forKey: BannerState.isSimpleProductsOnlyBannerDismissedKey) }, label: { Image(systemName: "xmark") .font(.posBodyRegular) @@ -110,7 +111,7 @@ private extension ItemListView { .shadow(color: Color.black.opacity(0.08), radius: 4, y: 2) .accessibilityAddTraits(.isButton) .onTapGesture { - viewModel.simpleProductsInfoButtonTapped() + showSimpleProductsModal = true } .padding(.bottom, Constants.bannerCardPadding) } @@ -126,7 +127,7 @@ private extension ItemListView { func listView(_ items: [any POSDisplayableItem]) -> some View { ScrollView { VStack { - if dynamicTypeSize.isAccessibilitySize, viewModel.shouldShowHeaderBanner { + if dynamicTypeSize.isAccessibilitySize, !isHeaderBannerDismissed { bannerCardView } ForEach(items, id: \.id) { item in @@ -138,13 +139,13 @@ private extension ItemListView { .background(GeometryReader { proxy in Color.clear .onChange(of: proxy.frame(in: .global).maxY) { maxY in - if case .loading = posModel.itemListState { + if case .loading = itemListState { return } let viewHeight = UIScreen.main.bounds.height if maxY < viewHeight { Task { - await viewModel.loadNextItems() + await loadNextItems() } } } @@ -204,15 +205,18 @@ private extension ItemListView { } } + +extension ItemListView { + struct BannerState { + static let isSimpleProductsOnlyBannerDismissedKey = "isSimpleProductsOnlyBannerDismissed" + } +} + #if DEBUG import class WooFoundation.MockAnalyticsPreview #Preview { - let posModel = PointOfSaleAggregateModel( - itemProvider: POSItemProviderPreview(), - cardPresentPaymentService: CardPresentPaymentPreviewService(), - orderService: POSOrderPreviewService(), - analytics: MockAnalyticsPreview()) - ItemListView(viewModel: ItemListViewModel(posModel: posModel), - posModel: posModel) + ItemListView(itemListState: .constant(.initialLoading), + loadNextItems: {}, + reloadItems: {}) } #endif diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift index be3403d34fe..7a1d39bb94c 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift @@ -4,30 +4,33 @@ struct PointOfSaleDashboardView: View { @ObservedObject private var viewModel: PointOfSaleDashboardViewModel @ObservedObject private var totalsViewModel: TotalsViewModel private let cartViewModel: CartViewModel - private let itemListViewModel: ItemListViewModel @ObservedObject private var posModel: PointOfSaleAggregateModel @State var showExitPOSModal: Bool = false @State var showSupport: Bool = false + @State private var itemListState: PointOfSaleItemListState = .initialLoading + + private let itemsService: POSItemsService + private var currentPage: Int = Constants.initialPage init(viewModel: PointOfSaleDashboardViewModel, totalsViewModel: TotalsViewModel, cartViewModel: CartViewModel, - itemListViewModel: ItemListViewModel, - posModel: PointOfSaleAggregateModel) { + posModel: PointOfSaleAggregateModel, + itemsService: POSItemsService) { self.viewModel = viewModel self.totalsViewModel = totalsViewModel self.cartViewModel = cartViewModel - self.itemListViewModel = itemListViewModel self.posModel = posModel + self.itemsService = itemsService } @State private var floatingSize: CGSize = .zero var body: some View { ZStack(alignment: .bottomLeading) { - switch posModel.itemListState { + switch itemListState { case .initialLoading: PointOfSaleLoadingView() .transition(.opacity) @@ -35,7 +38,7 @@ struct PointOfSaleDashboardView: View { case .error(let errorContents): PointOfSaleItemListErrorView(error: errorContents, onRetry: { Task { - await posModel.reloadItems() + await reloadItems() } }) case .empty: @@ -52,7 +55,7 @@ struct PointOfSaleDashboardView: View { .offset(x: Constants.floatingControlHorizontalOffset, y: -Constants.floatingControlVerticalOffset) .trackSize(size: $floatingSize) .accessibilitySortPriority(1) - .renderedIf(posModel.itemListState != .initialLoading) + .renderedIf(itemListState != .initialLoading) POSConnectivityView() .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) @@ -64,7 +67,7 @@ struct PointOfSaleDashboardView: View { CGSizeMake(floatingSize.width + Constants.floatingControlHorizontalOffset, floatingSize.height + Constants.floatingControlVerticalOffset)) .environment(\.posBackgroundAppearance, posModel.paymentState != .processingPayment ? .primary : .secondary) - .animation(.easeInOut, value: posModel.itemListState == .initialLoading) + .animation(.easeInOut, value: itemListState == .initialLoading) .animation(.easeInOut(duration: Constants.connectivityAnimationDuration), value: viewModel.showsConnectivityError) .background(Color.posPrimaryBackground) .navigationBarBackButtonHidden(true) @@ -87,7 +90,7 @@ struct PointOfSaleDashboardView: View { supportForm } .task { - await posModel.loadInitialItems() + await loadInitialItems() } } @@ -162,6 +165,56 @@ private extension PointOfSaleDashboardView { } } +private extension PointOfSaleDashboardView { + @MainActor + func loadInitialItems() async { + do { + itemListState = .initialLoading + try await fetchItems(pageNumber: 1, currentItems: []) + } catch { + itemListState = .error(PointOfSaleErrorState.errorOnLoadingProducts()) + } + } + + @MainActor + func loadNextItems() async { + // TODO: Optimize API calls. gh-14186 + // If there are no more pages to fetch, we can avoid the next call. + guard case .loaded(let currentItems) = itemListState else { + return + } + let nextPage = currentPage + 1 + await loadItems(pageNumber: nextPage, currentItems: currentItems) + } + + @MainActor + func loadItems(pageNumber: Int, currentItems: [any POSDisplayableItem]) async { + do { + itemListState = .loading(currentItems) + try await fetchItems(pageNumber: pageNumber, currentItems: currentItems) + } catch { + itemListState = .error(PointOfSaleErrorState.errorOnLoadingProducts()) + } + } + + @MainActor + func fetchItems(pageNumber: Int, currentItems: [any POSDisplayableItem]) async throws { + let allItems = try await itemsService.fetchItems(pageNumber: pageNumber, + currentItems: currentItems) + + if allItems.count == 0 { + itemListState = .empty + } else { + itemListState = .loaded(allItems) + } + } + + @MainActor + func reloadItems() async { + await loadItems(pageNumber: 1, currentItems: []) + } +} + struct FloatingControlAreaSizeKey: EnvironmentKey { static let defaultValue = CGSize.zero } @@ -183,6 +236,8 @@ private extension PointOfSaleDashboardView { static let exitPOSSheetMaxWidth: CGFloat = 900.0 static let supportTag = "origin:point-of-sale" static let connectivityAnimationDuration: CGFloat = 1.0 + + static let initialPage: Int = 1 } enum Localization { @@ -212,7 +267,9 @@ private extension PointOfSaleDashboardView { } var productListView: some View { - ItemListView(viewModel: itemListViewModel, posModel: posModel) + ItemListView(itemListState: $itemListState, + loadNextItems: loadNextItems, + reloadItems: reloadItems) } } @@ -223,7 +280,6 @@ import class WooFoundation.MockAnalyticsProviderPreview #Preview { let posModel = PointOfSaleAggregateModel( - itemProvider: POSItemProviderPreview(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderService: POSOrderPreviewService(), analytics: MockAnalyticsPreview()) @@ -232,7 +288,6 @@ import class WooFoundation.MockAnalyticsProviderPreview currencyFormatter: .init(currencySettings: .init())) let cartVM = CartViewModel(analytics: MockAnalyticsPreview(), posModel: posModel) - let itemsListVM = ItemListViewModel(posModel: posModel) let posVM = PointOfSaleDashboardViewModel( posModel: posModel, connectivityObserver: POSConnectivityObserverPreview()) @@ -241,8 +296,8 @@ import class WooFoundation.MockAnalyticsProviderPreview PointOfSaleDashboardView(viewModel: posVM, totalsViewModel: totalsVM, cartViewModel: cartVM, - itemListViewModel: itemsListVM, - posModel: posModel) + posModel: posModel, + itemsService: POSItemsService(itemProvider: POSItemProviderPreview())) } } #endif diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift index 055757fe9a1..6f1ff0ca0a8 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift @@ -8,9 +8,9 @@ struct PointOfSaleEntryPointView: View { @StateObject private var viewModel: PointOfSaleDashboardViewModel @StateObject private var totalsViewModel: TotalsViewModel @StateObject private var cartViewModel: CartViewModel - @StateObject private var itemListViewModel: ItemListViewModel @StateObject private var posModalManager = POSModalManager() @StateObject private var posModel: PointOfSaleAggregateModel + private var itemProvider: POSItemProvider private let onPointOfSaleModeActiveStateChange: ((Bool) -> Void) @@ -22,8 +22,9 @@ struct PointOfSaleEntryPointView: View { analytics: Analytics) { self.onPointOfSaleModeActiveStateChange = onPointOfSaleModeActiveStateChange + self.itemProvider = itemProvider + let posModel = PointOfSaleAggregateModel( - itemProvider: itemProvider, cardPresentPaymentService: cardPresentPaymentService, orderService: orderService, analytics: analytics) @@ -34,7 +35,6 @@ struct PointOfSaleEntryPointView: View { let cartViewModel = CartViewModel( analytics: analytics, posModel: posModel) - let itemListViewModel = ItemListViewModel(posModel: posModel) self._viewModel = StateObject(wrappedValue: PointOfSaleDashboardViewModel( posModel: posModel, @@ -42,7 +42,6 @@ struct PointOfSaleEntryPointView: View { ) self._cartViewModel = StateObject(wrappedValue: cartViewModel) self._totalsViewModel = StateObject(wrappedValue: totalsViewModel) - self._itemListViewModel = StateObject(wrappedValue: itemListViewModel) self._posModel = StateObject(wrappedValue: posModel) } @@ -50,8 +49,8 @@ struct PointOfSaleEntryPointView: View { PointOfSaleDashboardView(viewModel: viewModel, totalsViewModel: totalsViewModel, cartViewModel: cartViewModel, - itemListViewModel: itemListViewModel, - posModel: posModel) + posModel: posModel, + itemsService: POSItemsService(itemProvider: itemProvider)) .environmentObject(posModalManager) .environmentObject(posModel) .onAppear { diff --git a/WooCommerce/Classes/POS/Presentation/TotalsView.swift b/WooCommerce/Classes/POS/Presentation/TotalsView.swift index fe107815501..9e663485dcb 100644 --- a/WooCommerce/Classes/POS/Presentation/TotalsView.swift +++ b/WooCommerce/Classes/POS/Presentation/TotalsView.swift @@ -392,7 +392,6 @@ private extension View { import class WooFoundation.MockAnalyticsPreview #Preview { let posModel = PointOfSaleAggregateModel( - itemProvider: POSItemProviderPreview(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderService: POSOrderPreviewService(), analytics: MockAnalyticsPreview()) diff --git a/WooCommerce/Classes/POS/ViewModels/ItemListViewModel.swift b/WooCommerce/Classes/POS/ViewModels/ItemListViewModel.swift deleted file mode 100644 index 1fc96e30dd8..00000000000 --- a/WooCommerce/Classes/POS/ViewModels/ItemListViewModel.swift +++ /dev/null @@ -1,53 +0,0 @@ -import Combine -import SwiftUI -import protocol Yosemite.POSItem - -final class ItemListViewModel: ItemListViewModelProtocol { - let posModel: PointOfSaleAggregateModel - - @Published private(set) var isHeaderBannerDismissed: Bool = false - @Published var showSimpleProductsModal: Bool = false - - private var currentPage: Int = Constants.initialPage - - var shouldShowHeaderBanner: Bool { - // The banner it's shown as long as it hasn't already been dismissed once: - if UserDefaults.standard.bool(forKey: BannerState.isSimpleProductsOnlyBannerDismissedKey) == true { - return false - } - return !isHeaderBannerDismissed && posModel.itemListState != .empty - } - - init(posModel: PointOfSaleAggregateModel) { - self.posModel = posModel - } - - @MainActor - func loadNextItems() async { - // TODO: Optimize API calls. gh-14186 - // If there are no more pages to fetch, we can avoid the next call. - let nextPage = currentPage + 1 - await posModel.loadItems(pageNumber: nextPage) - } - - func dismissBanner() { - isHeaderBannerDismissed = true - UserDefaults.standard.set(isHeaderBannerDismissed, forKey: BannerState.isSimpleProductsOnlyBannerDismissedKey) - } - - func simpleProductsInfoButtonTapped() { - showSimpleProductsModal = true - } -} - -extension ItemListViewModel { - struct BannerState { - static let isSimpleProductsOnlyBannerDismissedKey = "isSimpleProductsOnlyBannerDismissed" - } -} - -private extension ItemListViewModel { - enum Constants { - static let initialPage: Int = 1 - } -} diff --git a/WooCommerce/Classes/POS/ViewModels/ItemListViewModelProtocol.swift b/WooCommerce/Classes/POS/ViewModels/ItemListViewModelProtocol.swift deleted file mode 100644 index be556d658b5..00000000000 --- a/WooCommerce/Classes/POS/ViewModels/ItemListViewModelProtocol.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Combine -import Foundation -import protocol Yosemite.POSItem - -protocol ItemListViewModelProtocol: ObservableObject { - var isHeaderBannerDismissed: Bool { get } - var shouldShowHeaderBanner: Bool { get } - - func loadNextItems() async - func dismissBanner() -} diff --git a/WooCommerce/Classes/POS/ViewModels/PointOfSaleDashboardViewModel.swift b/WooCommerce/Classes/POS/ViewModels/PointOfSaleDashboardViewModel.swift index 4f38647a796..5e735ca9cfa 100644 --- a/WooCommerce/Classes/POS/ViewModels/PointOfSaleDashboardViewModel.swift +++ b/WooCommerce/Classes/POS/ViewModels/PointOfSaleDashboardViewModel.swift @@ -11,8 +11,6 @@ final class PointOfSaleDashboardViewModel: ObservableObject { private let connectivityObserver: ConnectivityObserver - @Published var isError: Bool = false - @Published var isEmpty: Bool = false @Published var showsConnectivityError: Bool = false private var cancellables: Set = [] @@ -22,31 +20,10 @@ final class PointOfSaleDashboardViewModel: ObservableObject { self.posModel = posModel self.connectivityObserver = connectivityObserver - observeItemListState() observeConnectivity() } } -private extension PointOfSaleDashboardViewModel { - func observeItemListState() { - posModel.$itemListState - .receive(on: DispatchQueue.main) - .sink { [weak self] state in - guard let self = self else { return } - switch state { - case .error: - self.isError = true - case .empty: - self.isEmpty = true - default: - self.isError = false - self.isEmpty = false - } - } - .store(in: &cancellables) - } -} - private extension PointOfSaleDashboardViewModel { func observeConnectivity() { connectivityObserver.statusPublisher diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index e2617c860b8..6295a33fdd6 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -35,7 +35,6 @@ 0188CA0F2C65622A0051BF1C /* PointOfSaleCardPresentPaymentValidatingOrderErrorMessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0188CA0E2C65622A0051BF1C /* PointOfSaleCardPresentPaymentValidatingOrderErrorMessageViewModel.swift */; }; 0188CA112C6565320051BF1C /* PointOfSaleCardPresentPaymentValidatingOrderErrorMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0188CA102C6565320051BF1C /* PointOfSaleCardPresentPaymentValidatingOrderErrorMessageView.swift */; }; 018D5C7E2CA6B4A60085EBEE /* CurrencySettings+Sanitized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018D5C7D2CA6B49D0085EBEE /* CurrencySettings+Sanitized.swift */; }; - 019C5EB02C516F7B005B82D3 /* ItemListViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019C5EAF2C516F7B005B82D3 /* ItemListViewModelProtocol.swift */; }; 019C5EB22C5171E6005B82D3 /* MockItemListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019C5EB12C5171E6005B82D3 /* MockItemListViewModel.swift */; }; 01ADC1362C9AB4810036F7D2 /* PointOfSaleCardPresentPaymentIntentCreationErrorMessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01ADC1352C9AB4810036F7D2 /* PointOfSaleCardPresentPaymentIntentCreationErrorMessageViewModel.swift */; }; 01ADC1382C9AB6050036F7D2 /* PointOfSaleCardPresentPaymentIntentCreationErrorMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01ADC1372C9AB6050036F7D2 /* PointOfSaleCardPresentPaymentIntentCreationErrorMessageView.swift */; }; @@ -832,6 +831,7 @@ 20B7E3172CD100DC007FD997 /* PointOfSalePaymentState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20B7E3162CD100DC007FD997 /* PointOfSalePaymentState.swift */; }; 20B7E3192CD13CE3007FD997 /* PointOfSaleItemListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20B7E3182CD13CE3007FD997 /* PointOfSaleItemListState.swift */; }; 20B7E3512CD3DAB5007FD997 /* POSDisplayableItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20B7E3502CD3DAB5007FD997 /* POSDisplayableItem.swift */; }; + 20B7E3552CD4F59C007FD997 /* POSItemsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20B7E3542CD4F59C007FD997 /* POSItemsService.swift */; }; 20BBD62C2B3060A300A903F6 /* AddOrderComponentsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20BBD62B2B3060A300A903F6 /* AddOrderComponentsSection.swift */; }; 20BCF6EE2B0E478B00954840 /* WooPaymentsDepositsOverviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20BCF6ED2B0E478B00954840 /* WooPaymentsDepositsOverviewViewModel.swift */; }; 20BCF6F02B0E48CC00954840 /* WooPaymentsDepositsOverviewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20BCF6EF2B0E48CC00954840 /* WooPaymentsDepositsOverviewViewModelTests.swift */; }; @@ -1495,7 +1495,6 @@ 6832C7CA26DA5C4500BA4088 /* LabeledTextViewTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6832C7C926DA5C4500BA4088 /* LabeledTextViewTableViewCell.swift */; }; 6832C7CC26DA5FDF00BA4088 /* LabeledTextViewTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6832C7CB26DA5FDE00BA4088 /* LabeledTextViewTableViewCell.xib */; }; 683421642ACE9391009021D7 /* ProductDiscountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 683421632ACE9391009021D7 /* ProductDiscountView.swift */; }; - 683763162C2E44B800AD51D0 /* ItemListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 683763152C2E44B800AD51D0 /* ItemListViewModel.swift */; }; 683763182C2E497000AD51D0 /* ItemListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 683763172C2E497000AD51D0 /* ItemListViewModelTests.swift */; }; 6837631A2C2E6F5900AD51D0 /* CartViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 683763192C2E6F5900AD51D0 /* CartViewModelTests.swift */; }; 6837631C2C2E847D00AD51D0 /* CartViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6837631B2C2E847D00AD51D0 /* CartViewModel.swift */; }; @@ -3122,7 +3121,6 @@ 0188CA0E2C65622A0051BF1C /* PointOfSaleCardPresentPaymentValidatingOrderErrorMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentValidatingOrderErrorMessageViewModel.swift; sourceTree = ""; }; 0188CA102C6565320051BF1C /* PointOfSaleCardPresentPaymentValidatingOrderErrorMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentValidatingOrderErrorMessageView.swift; sourceTree = ""; }; 018D5C7D2CA6B49D0085EBEE /* CurrencySettings+Sanitized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CurrencySettings+Sanitized.swift"; sourceTree = ""; }; - 019C5EAF2C516F7B005B82D3 /* ItemListViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListViewModelProtocol.swift; sourceTree = ""; }; 019C5EB12C5171E6005B82D3 /* MockItemListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockItemListViewModel.swift; sourceTree = ""; }; 01ADC1352C9AB4810036F7D2 /* PointOfSaleCardPresentPaymentIntentCreationErrorMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentIntentCreationErrorMessageViewModel.swift; sourceTree = ""; }; 01ADC1372C9AB6050036F7D2 /* PointOfSaleCardPresentPaymentIntentCreationErrorMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentIntentCreationErrorMessageView.swift; sourceTree = ""; }; @@ -3924,6 +3922,7 @@ 20B7E3162CD100DC007FD997 /* PointOfSalePaymentState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSalePaymentState.swift; sourceTree = ""; }; 20B7E3182CD13CE3007FD997 /* PointOfSaleItemListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleItemListState.swift; sourceTree = ""; }; 20B7E3502CD3DAB5007FD997 /* POSDisplayableItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSDisplayableItem.swift; sourceTree = ""; }; + 20B7E3542CD4F59C007FD997 /* POSItemsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSItemsService.swift; sourceTree = ""; }; 20BBD62B2B3060A300A903F6 /* AddOrderComponentsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddOrderComponentsSection.swift; sourceTree = ""; }; 20BCF6ED2B0E478B00954840 /* WooPaymentsDepositsOverviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooPaymentsDepositsOverviewViewModel.swift; sourceTree = ""; }; 20BCF6EF2B0E48CC00954840 /* WooPaymentsDepositsOverviewViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooPaymentsDepositsOverviewViewModelTests.swift; sourceTree = ""; }; @@ -4521,7 +4520,6 @@ 6832C7C926DA5C4500BA4088 /* LabeledTextViewTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabeledTextViewTableViewCell.swift; sourceTree = ""; }; 6832C7CB26DA5FDE00BA4088 /* LabeledTextViewTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LabeledTextViewTableViewCell.xib; sourceTree = ""; }; 683421632ACE9391009021D7 /* ProductDiscountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductDiscountView.swift; sourceTree = ""; }; - 683763152C2E44B800AD51D0 /* ItemListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListViewModel.swift; sourceTree = ""; }; 683763172C2E497000AD51D0 /* ItemListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListViewModelTests.swift; sourceTree = ""; }; 683763192C2E6F5900AD51D0 /* CartViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartViewModelTests.swift; sourceTree = ""; }; 6837631B2C2E847D00AD51D0 /* CartViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartViewModel.swift; sourceTree = ""; }; @@ -6788,8 +6786,6 @@ isa = PBXGroup; children = ( 026826922BF59D830036F959 /* PointOfSaleDashboardViewModel.swift */, - 019C5EAF2C516F7B005B82D3 /* ItemListViewModelProtocol.swift */, - 683763152C2E44B800AD51D0 /* ItemListViewModel.swift */, C7F746C82C4868670023ADD0 /* TotalsViewModelProtocol.swift */, 6885E2CB2C32B14B004C8D70 /* TotalsViewModel.swift */, 016BCAFE2C4F907F009D8367 /* CartViewModelProtocol.swift */, @@ -7103,6 +7099,7 @@ 026826972BF59D9E0036F959 /* Utils */, 026826A12BF59DED0036F959 /* Presentation */, 20B7E3122CCFFEB4007FD997 /* PointOfSaleAggregateModel.swift */, + 20B7E3542CD4F59C007FD997 /* POSItemsService.swift */, 20B7E3502CD3DAB5007FD997 /* POSDisplayableItem.swift */, 20B7E3182CD13CE3007FD997 /* PointOfSaleItemListState.swift */, 20B7E3162CD100DC007FD997 /* PointOfSalePaymentState.swift */, @@ -14880,7 +14877,6 @@ 683DF5FF2C6AF46500A5CDC6 /* POSHeaderTitleView.swift in Sources */, DEA90D392C9156A20021ABC3 /* BlazeCampaignDetailWebViewModel.swift in Sources */, DE7842F726F2E9340030C792 /* UIViewController+Connectivity.swift in Sources */, - 019C5EB02C516F7B005B82D3 /* ItemListViewModelProtocol.swift in Sources */, E1C47209267A1ECC00D06DA1 /* CrashLoggingStack.swift in Sources */, B991F5382B7BCA27001ADF83 /* LocallyStoredStateNameRetriever.swift in Sources */, CCF87BBE279047BC00461C43 /* InfiniteScrollList.swift in Sources */, @@ -14888,6 +14884,7 @@ DE7E5E8C2B4E9353002E28D2 /* ErrorStateView.swift in Sources */, 20ADE9412C6A02B700C91265 /* POSErrorXMark.swift in Sources */, DE63115B2AF1E13200587641 /* WPComLoginCoordinator.swift in Sources */, + 20B7E3552CD4F59C007FD997 /* POSItemsService.swift in Sources */, EE3B17B62AA03837004D3E0C /* CelebrationView.swift in Sources */, D85A3C5226C15DE200C0E026 /* InPersonPaymentsPluginNotSupportedVersionView.swift in Sources */, EE57C11D297AC27300BC31E7 /* TrackEventRequestNotificationHandler.swift in Sources */, @@ -15103,7 +15100,6 @@ 6837631C2C2E847D00AD51D0 /* CartViewModel.swift in Sources */, 45CDAFAE2434CFCA00F83C22 /* ProductCatalogVisibilityViewController.swift in Sources */, D85B8333222FABD1002168F3 /* StatusListTableViewCell.swift in Sources */, - 683763162C2E44B800AD51D0 /* ItemListViewModel.swift in Sources */, DE2E8EB729547771002E4B14 /* ApplicationPasswordDisabledViewModel.swift in Sources */, 0259D65D2582248D003B1CD6 /* PrintShippingLabelViewController.swift in Sources */, D881A31B256B5CC500FE5605 /* ULErrorViewController.swift in Sources */,