Skip to content

Commit

Permalink
Remove dashboard usage of cart and itemlist VMs
Browse files Browse the repository at this point in the history
  • Loading branch information
joshheald committed Oct 30, 2024
1 parent d5cf577 commit 3c48363
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 201 deletions.
65 changes: 64 additions & 1 deletion WooCommerce/Classes/POS/PointOfSaleAggregateModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ final class PointOfSaleAggregateModel: ObservableObject {
@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
Expand Down Expand Up @@ -57,13 +58,45 @@ final class PointOfSaleAggregateModel: ObservableObject {
}

@MainActor
func loadItems(pageNumber: Int) async throws {
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
try await fetchItems(pageNumber: pageNumber)
} catch {
itemListState = .error(PointOfSaleErrorState.errorOnLoadingProducts())
}
}

@MainActor
private func fetchItems(pageNumber: Int) async throws {
let newItems = try await itemProvider.providePointOfSaleItems(pageNumber: pageNumber)
let uniqueNewItems = newItems.filter { newItem in
!allItems.contains(where: { $0.productID == newItem.productID })
}

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() {
Expand Down Expand Up @@ -257,3 +290,33 @@ extension PointOfSaleAggregateModel {
}
}
}

struct PointOfSaleErrorState: Equatable {
let title: String
let subtitle: String
let buttonText: String

static func errorOnLoadingProducts() -> Self {
PointOfSaleErrorState(title: Constants.failedToLoadTitle,
subtitle: Constants.failedToLoadSubtitle,
buttonText: Constants.failedToLoadButtonTitle)
}

enum Constants {
static let failedToLoadTitle = NSLocalizedString(
"pos.itemList.failedToLoadTitle",
value: "Error loading products",
comment: "Text appearing on the item list screen when there's an error loading products."
)
static let failedToLoadSubtitle = NSLocalizedString(
"pos.itemList.failedToLoadSubtitle",
value: "Give it another go?",
comment: "Text appearing on the item list screen as subtitle when there's an error loading products."
)
static let failedToLoadButtonTitle = NSLocalizedString(
"pos.itemList.failedToLoadButtonTitle",
value: "Retry",
comment: "Text for the button appearing on the item list screen when there's an error loading products."
)
}
}
54 changes: 54 additions & 0 deletions WooCommerce/Classes/POS/PointOfSaleItemListState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import protocol Yosemite.POSItem

enum PointOfSaleItemListState: Equatable {
case empty
case initialLoading
case loading
case loaded([POSItem])
case error(PointOfSaleErrorState)

var isLoaded: Bool {
switch self {
case .loaded:
return true
default:
return false
}
}

var isLoading: Bool {
switch self {
case .loading:
return true
default:
return false
}
}

var hasError: PointOfSaleErrorState {
switch self {
case .error(let errorModel):
return errorModel
default:
return PointOfSaleErrorState(title: "Unknown error",
subtitle: "Unknown error",
buttonText: "Retry")
}
}

// Equatable conformance for testing:
static func == (lhs: PointOfSaleItemListState, rhs: PointOfSaleItemListState) -> Bool {
switch (lhs, rhs) {
case (.initialLoading, .initialLoading),
(.empty, .empty),
(.loading, .loading):
return true
case (.loaded(let lhsItems), .loaded(let rhsItems)):
return lhsItems.map { $0.itemID } == rhsItems.map { $0.itemID }
case (.error(let lhsError), .error(let rhsError)):
return lhsError == rhsError
default:
return false
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import SwiftUI

struct PointOfSaleItemListErrorView: View {
private var error: ItemListViewModel.ErrorModel
private var error: PointOfSaleErrorState
private var onRetry: (() -> Void)? = nil

init(error: ItemListViewModel.ErrorModel, onRetry: (() -> Void)? = nil) {
init(error: PointOfSaleErrorState, onRetry: (() -> Void)? = nil) {
self.error = error
self.onRetry = onRetry
}
Expand Down
22 changes: 17 additions & 5 deletions WooCommerce/Classes/POS/Presentation/CartView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,26 @@ struct CartView: View {
@Environment(\.colorScheme) var colorScheme

@State private var offSetPosition: CGFloat = 0.0

private var coordinateSpace: CoordinateSpace = .named(Constants.scrollViewCoordinateSpaceIdentifier)

private var shouldApplyHeaderBottomShadow: Bool {
!posModel.cart.isEmpty && offSetPosition < 0
}

private var isAddMoreDisabled: Bool {
switch posModel.paymentState {
case .processingPayment,
.paymentError,
.cardPaymentSuccessful,
.validatingOrder,
.preparingReader:
return true
case .idle, .validatingOrderError, .acceptingCard:
return posModel.orderState.isSyncing
}
}

init(posModel: PointOfSaleAggregateModel,
viewModel: PointOfSaleDashboardViewModel,
cartViewModel: CartViewModel) {
Expand All @@ -29,8 +44,8 @@ struct CartView: View {
DynamicHStack(spacing: Constants.cartHeaderSpacing) {
HStack(spacing: Constants.cartHeaderElementSpacing) {
backAddMoreButton
.disabled(viewModel.isAddMoreDisabled)
.shimmering(active: viewModel.isAddMoreDisabled)
.disabled(isAddMoreDisabled)
.shimmering(active: isAddMoreDisabled)

HStack {
Text(Localization.cartTitle)
Expand Down Expand Up @@ -267,9 +282,6 @@ import class WooFoundation.MockAnalyticsProviderPreview
let itemsListViewModel = ItemListViewModel(posModel: posModel)
let dashboardViewModel = PointOfSaleDashboardViewModel(
posModel: posModel,
totalsViewModel: totalsViewModel,
cartViewModel: cartViewModel,
itemListViewModel: itemsListViewModel,
connectivityObserver: POSConnectivityObserverPreview())
CartView(posModel: posModel,
viewModel: dashboardViewModel,
Expand Down
19 changes: 13 additions & 6 deletions WooCommerce/Classes/POS/Presentation/ItemListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@ 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) {
init(viewModel: ItemListViewModel,
posModel: PointOfSaleAggregateModel) {
self.viewModel = viewModel
self.posModel = posModel
}

var body: some View {
VStack {
headerView
switch viewModel.state {
case .empty, .error:
.posModal(isPresented: $viewModel.showSimpleProductsModal) {
SimpleProductsOnlyInformation(isPresented: $viewModel.showSimpleProductsModal)
}
switch posModel.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
EmptyView()
Expand All @@ -23,7 +29,7 @@ struct ItemListView: View {
}
}
.refreshable {
await viewModel.reload()
await posModel.reloadItems()
}
.background(Color.posPrimaryBackground)
.accessibilityElement(children: .contain)
Expand Down Expand Up @@ -136,7 +142,7 @@ private extension ItemListView {
.background(GeometryReader { proxy in
Color.clear
.onChange(of: proxy.frame(in: .global).maxY) { maxY in
if viewModel.state == .loading {
if posModel.itemListState == .loading {
return
}
let viewHeight = UIScreen.main.bounds.height
Expand Down Expand Up @@ -210,6 +216,7 @@ import class WooFoundation.MockAnalyticsPreview
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderService: POSOrderPreviewService(),
analytics: MockAnalyticsPreview())
ItemListView(viewModel: ItemListViewModel(posModel: posModel))
ItemListView(viewModel: ItemListViewModel(posModel: posModel),
posModel: posModel)
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ struct POSFloatingControlView: View {
@Binding var showExitPOSModal: Bool
@Binding var showSupport: Bool

private var isExitPOSDisabled: Bool {
switch posModel.paymentState {
case .processingPayment:
return true
case .idle,
.acceptingCard,
.validatingOrder,
.validatingOrderError,
.preparingReader,
.paymentError,
.cardPaymentSuccessful:
return false
}
}

init(posModel: PointOfSaleAggregateModel,
showExitPOSModal: Binding<Bool>,
showSupport: Binding<Bool>) {
Expand Down Expand Up @@ -46,7 +61,7 @@ struct POSFloatingControlView: View {
}
.background(backgroundColor)
.cornerRadius(Constants.cornerRadius)
.disabled(posModel.paymentState.cardHasBeenTapped)
.disabled(isExitPOSDisabled)

CardReaderConnectionStatusView(posModel: posModel)
.foregroundStyle(fontColor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import SwiftUI
struct PointOfSaleDashboardView: View {
@ObservedObject private var viewModel: PointOfSaleDashboardViewModel
@ObservedObject private var totalsViewModel: TotalsViewModel
@ObservedObject private var cartViewModel: CartViewModel
@ObservedObject private var itemListViewModel: ItemListViewModel
private let cartViewModel: CartViewModel
private let itemListViewModel: ItemListViewModel

@ObservedObject private var posModel: PointOfSaleAggregateModel

Expand All @@ -27,15 +27,15 @@ struct PointOfSaleDashboardView: View {

var body: some View {
ZStack(alignment: .bottomLeading) {
if viewModel.isInitialLoading {
if posModel.itemListState == .initialLoading {
PointOfSaleLoadingView()
.transition(.opacity)
.ignoresSafeArea()
} else if viewModel.isError {
let errorContents = viewModel.itemListViewModel.state.hasError
let errorContents = posModel.itemListState.hasError
PointOfSaleItemListErrorView(error: errorContents, onRetry: {
Task {
await viewModel.itemListViewModel.reload()
await posModel.reloadItems()
}
})
} else if viewModel.isEmpty {
Expand All @@ -51,7 +51,7 @@ struct PointOfSaleDashboardView: View {
.offset(x: Constants.floatingControlHorizontalOffset, y: -Constants.floatingControlVerticalOffset)
.trackSize(size: $floatingSize)
.accessibilitySortPriority(1)
.renderedIf(!viewModel.isInitialLoading)
.renderedIf(posModel.itemListState != .initialLoading)

POSConnectivityView()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
Expand All @@ -63,7 +63,7 @@ struct PointOfSaleDashboardView: View {
CGSizeMake(floatingSize.width + Constants.floatingControlHorizontalOffset,
floatingSize.height + Constants.floatingControlVerticalOffset))
.environment(\.posBackgroundAppearance, posModel.paymentState != .processingPayment ? .primary : .secondary)
.animation(.easeInOut, value: viewModel.isInitialLoading)
.animation(.easeInOut, value: posModel.itemListState == .initialLoading)
.animation(.easeInOut(duration: Constants.connectivityAnimationDuration), value: viewModel.showsConnectivityError)
.background(Color.posPrimaryBackground)
.navigationBarBackButtonHidden(true)
Expand All @@ -77,9 +77,6 @@ struct PointOfSaleDashboardView: View {
PointOfSaleCardPresentPaymentAlert(alertType: alertType)
.posInteractiveDismissDisabled(alertType.isDismissDisabled)
}
.posModal(isPresented: $itemListViewModel.showSimpleProductsModal) {
SimpleProductsOnlyInformation(isPresented: $itemListViewModel.showSimpleProductsModal)
}
.posModal(isPresented: $showExitPOSModal) {
PointOfSaleExitPosAlertView(isPresented: $showExitPOSModal)
.frame(maxWidth: Constants.exitPOSSheetMaxWidth)
Expand All @@ -89,7 +86,7 @@ struct PointOfSaleDashboardView: View {
supportForm
}
.task {
await viewModel.itemListViewModel.loadInitialItems()
await posModel.loadInitialItems()
}
}

Expand Down Expand Up @@ -214,7 +211,7 @@ private extension PointOfSaleDashboardView {
}

var productListView: some View {
ItemListView(viewModel: itemListViewModel)
ItemListView(viewModel: itemListViewModel, posModel: posModel)
}
}

Expand All @@ -237,9 +234,6 @@ import class WooFoundation.MockAnalyticsProviderPreview
let itemsListVM = ItemListViewModel(posModel: posModel)
let posVM = PointOfSaleDashboardViewModel(
posModel: posModel,
totalsViewModel: totalsVM,
cartViewModel: cartVM,
itemListViewModel: itemsListVM,
connectivityObserver: POSConnectivityObserverPreview())

NavigationStack {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ struct PointOfSaleEntryPointView: View {

self._viewModel = StateObject(wrappedValue: PointOfSaleDashboardViewModel(
posModel: posModel,
totalsViewModel: totalsViewModel,
cartViewModel: cartViewModel,
itemListViewModel: itemListViewModel,
connectivityObserver: ServiceLocator.connectivityObserver)
)
self._cartViewModel = StateObject(wrappedValue: cartViewModel)
Expand Down
Loading

0 comments on commit 3c48363

Please sign in to comment.