Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ protocol PointOfSaleAggregateModelProtocol {
var orderState: PointOfSaleOrderState { orderController.orderState.externalState }
private var internalOrderState: PointOfSaleInternalOrderState { orderController.orderState }

let entryPointController: POSEntryPointController
let purchasableItemsController: PointOfSaleItemsControllerProtocol
let purchasableItemsSearchController: PointOfSaleSearchingItemsControllerProtocol
let popularPurchasableItemsController: PointOfSaleItemsControllerProtocol
Expand Down Expand Up @@ -99,7 +100,8 @@ protocol PointOfSaleAggregateModelProtocol {
_viewStateCoordinator
}

init(itemsController: PointOfSaleItemsControllerProtocol,
init(entryPointController: POSEntryPointController,
itemsController: PointOfSaleItemsControllerProtocol,
purchasableItemsSearchController: PointOfSaleSearchingItemsControllerProtocol,
couponsController: PointOfSaleCouponsControllerProtocol,
couponsSearchController: PointOfSaleSearchingItemsControllerProtocol,
Expand All @@ -112,6 +114,7 @@ protocol PointOfSaleAggregateModelProtocol {
barcodeScanService: PointOfSaleBarcodeScanServiceProtocol,
soundPlayer: PointOfSaleSoundPlayerProtocol = PointOfSaleSoundPlayer(),
paymentState: PointOfSalePaymentState = .idle) {
self.entryPointController = entryPointController
self.purchasableItemsController = itemsController
self.purchasableItemsSearchController = purchasableItemsSearchController
self.couponsController = couponsController
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import SwiftUI

struct PointOfSaleLoadingView: View {
@State private var waitingTimeTracker: WaitingTimeTracker?

var body: some View {
HStack(alignment: .center) {
Spacer()
Expand All @@ -15,28 +13,10 @@ struct PointOfSaleLoadingView: View {
.multilineTextAlignment(.center)
Spacer()
}
.onAppear {
trackTimeOnAppear()
}
.onDisappear {
trackElapsedTimeOnDisappear()
}
.background(Color.posSurface)
}
}

private extension PointOfSaleLoadingView {
func trackTimeOnAppear() {
waitingTimeTracker = WaitingTimeTracker(trackScenario: .pointOfSaleLoaded)
}

func trackElapsedTimeOnDisappear() {
if let waitingTimeTracker = waitingTimeTracker {
waitingTimeTracker.end(using: .milliseconds)
}
}
}

#Preview {
PointOfSaleLoadingView()
}
86 changes: 55 additions & 31 deletions WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,35 +32,56 @@ struct PointOfSaleDashboardView: View {
}
}

// MARK: View State

enum ViewState: Equatable {
case loading
case ineligible(reason: POSIneligibleReason)
case error(PointOfSaleErrorState)
case content
case unsupportedWidth
}

private var viewState: ViewState {
PointOfSaleDashboardViewHelper.determineViewState(
eligibilityState: posModel.entryPointController.eligibilityState,
itemsContainerState: itemsViewState.containerState,
horizontalSizeClass: horizontalSizeClass
)
}

var body: some View {
@Bindable var posModel = posModel
ZStack(alignment: .bottomLeading) {
if case .regular = horizontalSizeClass {
switch itemsViewState.containerState {
case .loading:
PointOfSaleLoadingView()
.transition(.opacity)
.ignoresSafeArea()
case .error(let error):
PointOfSaleItemListFullscreenErrorView(error: error, onAction: {
Task {
switch viewStateCoordinator.selectedItemListType {
case .products(search: false):
await posModel.purchasableItemsController.loadItems(base: .root)
case .products(search: true):
await posModel.purchasableItemsSearchController.loadItems(base: .root)
case .coupons(search: false):
await posModel.couponsSearchController.loadItems(base: .root)
case .coupons(search: true):
await posModel.couponsSearchController.loadItems(base: .root)
}
switch viewState {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

much cleaner view hierarchy 💯

case .loading:
PointOfSaleLoadingView()
.transition(.opacity)
.ignoresSafeArea()
case .ineligible(let reason):
POSIneligibleView(reason: reason, onRefresh: {
try await posModel.entryPointController.refreshEligibility(reason: reason)
})
.frame(maxWidth: .infinity)
case .error(let error):
PointOfSaleItemListFullscreenErrorView(error: error, onAction: {
Task {
switch viewStateCoordinator.selectedItemListType {
case .products(search: false):
await posModel.purchasableItemsController.loadItems(base: .root)
case .products(search: true):
await posModel.purchasableItemsSearchController.loadItems(base: .root)
case .coupons(search: false):
await posModel.couponsSearchController.loadItems(base: .root)
case .coupons(search: true):
await posModel.couponsSearchController.loadItems(base: .root)
}
})
case .content:
contentView
.accessibilitySortPriority(2)
}
} else {
}
})
case .content:
contentView
.accessibilitySortPriority(2)
case .unsupportedWidth:
PointOfSaleUnsupportedWidthView()
.transition(.opacity)
.ignoresSafeArea()
Expand All @@ -73,15 +94,15 @@ struct PointOfSaleDashboardView: View {
.padding(.bottom, Constants.floatingControlBottomPadding)
.trackSize(size: $floatingSize)
.accessibilitySortPriority(1)
.renderedIf(itemsViewState.containerState != .loading)
.renderedIf(viewState.showsFloatingControl)

POSConnectivityView()
}
.environment(\.floatingControlAreaSize,
CGSizeMake(floatingSize.width + Constants.floatingControlHorizontalOffset,
floatingSize.height + Constants.floatingControlVerticalOffset))
.environment(\.posBackgroundAppearance, backgroundAppearance)
.animation(.easeInOut, value: itemsViewState.containerState == .loading)
.animation(.easeInOut, value: viewState == .loading)
.background(Color.posSurface)
.navigationBarBackButtonHidden(true)
.posModal(item: $posModel.cardPresentPaymentOnboardingViewModel, onDismiss: {
Expand All @@ -108,10 +129,13 @@ struct PointOfSaleDashboardView: View {
.sheet(isPresented: $showDocumentation) {
documentationView
}
.task {
await posModel.purchasableItemsController.loadItems(base: .root)
await posModel.couponsController.loadItems(base: .root)
await posModel.popularPurchasableItemsController.loadItems(base: .root)
.onChange(of: posModel.entryPointController.eligibilityState) { oldValue, newValue in
guard newValue == .eligible else { return }
Task { @MainActor in
await posModel.purchasableItemsController.loadItems(base: .root)
await posModel.couponsController.loadItems(base: .root)
await posModel.popularPurchasableItemsController.loadItems(base: .root)
}
}
.ignoresSafeArea(.keyboard)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,27 +50,19 @@ struct PointOfSaleEntryPointView: View {

var body: some View {
Group {
switch posEntryPointController.eligibilityState {
case .none:
if let posModel {
PointOfSaleDashboardView()
.environment(posModel)
} else {
PointOfSaleLoadingView()
case .eligible:
if let posModel = posModel {
PointOfSaleDashboardView()
.environment(posModel)
} else {
PointOfSaleLoadingView()
}
case let .ineligible(reason):
POSIneligibleView(reason: reason, onRefresh: {
try await posEntryPointController.refreshEligibility(reason: reason)
})
}
}
.task {
// We create the posModel in a task, not init, to avoid creating multiple copies during the view's lifecycle.
// Confusingly, init can be called more than once, but `task` matches the lifecycle.
// See https://developer.apple.com/documentation/swiftui/state#Store-observable-objects for details.
posModel = PointOfSaleAggregateModel(
entryPointController: posEntryPointController,
itemsController: itemsController,
purchasableItemsSearchController: purchasableItemsSearchController,
couponsController: couponsController,
Expand Down
1 change: 1 addition & 0 deletions WooCommerce/Classes/POS/Utils/PreviewHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ struct POSPreviewHelpers {
barcodeScanService: PointOfSaleBarcodeScanServiceProtocol = PointOfSalePreviewBarcodeScanService()
) -> PointOfSaleAggregateModel {
return PointOfSaleAggregateModel(
entryPointController: POSEntryPointController(eligibilityChecker: LegacyPOSTabEligibilityChecker(siteID: 0)),
itemsController: itemsController,
purchasableItemsSearchController: purchasableItemsSearchController,
couponsController: couponsController,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Foundation
import SwiftUI

@available(iOS 17.0, *)
struct PointOfSaleDashboardViewHelper {
static func determineViewState(
eligibilityState: POSEligibilityState?,
itemsContainerState: ItemsContainerState,
horizontalSizeClass: UserInterfaceSizeClass?
) -> PointOfSaleDashboardView.ViewState {
guard case .regular = horizontalSizeClass else {
return .unsupportedWidth
}

guard let eligibilityState else {
return .loading
}

switch eligibilityState {
case .eligible:
switch itemsContainerState {
case .loading:
return .loading
case .error(let error):
return .error(error)
case .content:
return .content
}
case .ineligible(let reason):
return .ineligible(reason: reason)
}
}
}

@available(iOS 17.0, *)
extension PointOfSaleDashboardView.ViewState {
var showsFloatingControl: Bool {
switch self {
case .content, .error, .unsupportedWidth:
return true
case .loading, .ineligible:
return false
}
}
}
8 changes: 8 additions & 0 deletions WooCommerce/WooCommerce.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,8 @@
026826C22BF59E410036F959 /* PointOfSaleCardPresentPaymentRequiredReaderUpdateInProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026826B92BF59E400036F959 /* PointOfSaleCardPresentPaymentRequiredReaderUpdateInProgressView.swift */; };
026826C42BF59E410036F959 /* PointOfSaleCardPresentPaymentFoundReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026826BB2BF59E410036F959 /* PointOfSaleCardPresentPaymentFoundReaderView.swift */; };
026826C72BF59E410036F959 /* PointOfSaleCardPresentPaymentScanningForReadersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026826BE2BF59E410036F959 /* PointOfSaleCardPresentPaymentScanningForReadersView.swift */; };
026878D62E293E7C00DBFD34 /* PointOfSaleDashboardViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026878D52E293E7300DBFD34 /* PointOfSaleDashboardViewHelper.swift */; };
026878D82E2942E400DBFD34 /* PointOfSaleDashboardViewHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026878D72E2942E200DBFD34 /* PointOfSaleDashboardViewHelperTests.swift */; };
02691780232600A6002AFC20 /* ProductsTabProductViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0269177F232600A6002AFC20 /* ProductsTabProductViewModelTests.swift */; };
02691782232605B9002AFC20 /* PaginatedListViewControllerStateCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02691781232605B9002AFC20 /* PaginatedListViewControllerStateCoordinatorTests.swift */; };
0269576A23726304001BA0BF /* KeyboardFrameObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0269576923726304001BA0BF /* KeyboardFrameObserver.swift */; };
Expand Down Expand Up @@ -3528,6 +3530,8 @@
026826B92BF59E400036F959 /* PointOfSaleCardPresentPaymentRequiredReaderUpdateInProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentRequiredReaderUpdateInProgressView.swift; sourceTree = "<group>"; };
026826BB2BF59E410036F959 /* PointOfSaleCardPresentPaymentFoundReaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentFoundReaderView.swift; sourceTree = "<group>"; };
026826BE2BF59E410036F959 /* PointOfSaleCardPresentPaymentScanningForReadersView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentScanningForReadersView.swift; sourceTree = "<group>"; };
026878D52E293E7300DBFD34 /* PointOfSaleDashboardViewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleDashboardViewHelper.swift; sourceTree = "<group>"; };
026878D72E2942E200DBFD34 /* PointOfSaleDashboardViewHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleDashboardViewHelperTests.swift; sourceTree = "<group>"; };
0269177F232600A6002AFC20 /* ProductsTabProductViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsTabProductViewModelTests.swift; sourceTree = "<group>"; };
02691781232605B9002AFC20 /* PaginatedListViewControllerStateCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedListViewControllerStateCoordinatorTests.swift; sourceTree = "<group>"; };
0269576923726304001BA0BF /* KeyboardFrameObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardFrameObserver.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -7085,6 +7089,7 @@
026826912BF59D7A0036F959 /* ViewHelpers */ = {
isa = PBXGroup;
children = (
026878D52E293E7300DBFD34 /* PointOfSaleDashboardViewHelper.swift */,
6891C3632D364AFB00B5B48C /* CollectCashViewHelper.swift */,
6885E2CB2C32B14B004C8D70 /* TotalsViewHelper.swift */,
6837631B2C2E847D00AD51D0 /* CartViewHelper.swift */,
Expand Down Expand Up @@ -13016,6 +13021,7 @@
children = (
683763192C2E6F5900AD51D0 /* CartViewHelperTests.swift */,
6891C3652D364C1A00B5B48C /* CollectCashViewHelperTests.swift */,
026878D72E2942E200DBFD34 /* PointOfSaleDashboardViewHelperTests.swift */,
208628732D48E4CB003F45DC /* TotalsViewHelperTests.swift */,
);
path = ViewHelpers;
Expand Down Expand Up @@ -16278,6 +16284,7 @@
68E674AD2A4DAC010034BA1E /* CurrentPlanDetailsView.swift in Sources */,
20134CE82D4D38E000076A80 /* CardPresentPaymentPlugin+SetUpTapToPay.swift in Sources */,
DE68B81F26F86B1700C86CFB /* OfflineBannerView.swift in Sources */,
026878D62E293E7C00DBFD34 /* PointOfSaleDashboardViewHelper.swift in Sources */,
B90D21802D1ED1F300ED60ED /* WooShippingCustomsItemOriginCountryInfoDialog.swift in Sources */,
D8610BCC256F284700A5DF27 /* ULErrorViewModel.swift in Sources */,
CCFC50552743BC0D001E505F /* OrderForm.swift in Sources */,
Expand Down Expand Up @@ -16930,6 +16937,7 @@
019130212CF5B0FF008C0C88 /* TapToPayEducationViewModelTests.swift in Sources */,
026A50302D2F80B5002C42C2 /* ThresholdInfiniteScrollTriggerDeterminerTests.swift in Sources */,
CC33238C29CDF67D00CA9709 /* ComponentSettingsViewModelTests.swift in Sources */,
026878D82E2942E400DBFD34 /* PointOfSaleDashboardViewHelperTests.swift in Sources */,
86F5FFE42CA30D9200C767C4 /* CustomFieldsListViewModelTests.swift in Sources */,
01AB2D162DDC8CDA00AA67FD /* MockAnalytics.swift in Sources */,
0261F5A728D454CF00B7AC72 /* ProductSearchUICommandTests.swift in Sources */,
Expand Down
Loading