Skip to content

Commit 3f3b654

Browse files
committed
Move catalogSyncState checking logic to ObservableItemsController
1 parent 0232f01 commit 3f3b654

14 files changed

+136
-249
lines changed

Modules/Sources/PointOfSale/Controllers/PointOfSaleItemsController.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ protocol PointOfSaleSearchingItemsControllerProtocol: PointOfSaleItemsController
4040

4141
init(itemProvider: PointOfSaleItemServiceProtocol,
4242
itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactoryProtocol,
43-
initialState: ItemsViewState = ItemsViewState(containerState: .loading,
43+
initialState: ItemsViewState = ItemsViewState(containerState: .loading(),
4444
itemsStack: ItemsStackState(root: .initial,
4545
itemStates: [:])),
4646
analyticsProvider: POSAnalyticsProviding) {
@@ -213,7 +213,7 @@ private extension PointOfSaleItemsController {
213213
func setRootLoadingState() {
214214
let items = itemsViewState.itemsStack.root.items
215215

216-
let isInitialState = itemsViewState.containerState == .loading && itemsViewState.itemsStack.root == .initial
216+
let isInitialState = itemsViewState.containerState == .loading() && itemsViewState.itemsStack.root == .initial
217217
if isInitialState {
218218
// Transition from initial to loading on first load
219219
itemsViewState.itemsStack.root = .loading([])

Modules/Sources/PointOfSale/Controllers/PointOfSaleObservableItemsController.swift

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ final class PointOfSaleObservableItemsController: PointOfSaleItemsControllerProt
4545
currencySettings: currencySettings
4646
)
4747
self.catalogSyncCoordinator = catalogSyncCoordinator
48+
49+
preloadloadLastFullSyncState()
4850
}
4951

5052
// periphery:ignore - used by tests
@@ -54,6 +56,8 @@ final class PointOfSaleObservableItemsController: PointOfSaleItemsControllerProt
5456
self.siteID = siteID
5557
self.dataSource = dataSource
5658
self.catalogSyncCoordinator = catalogSyncCoordinator
59+
60+
preloadloadLastFullSyncState()
5761
}
5862

5963
func loadItems(base: ItemListBaseItem) async {
@@ -117,13 +121,29 @@ final class PointOfSaleObservableItemsController: PointOfSaleItemsControllerProt
117121
// MARK: - State Computation
118122
private extension PointOfSaleObservableItemsController {
119123
var containerState: ItemsContainerState {
120-
// Use .loading during initial load, .content otherwise
124+
if isInitialCatalogSync {
125+
return .loading(isCatalogSyncing: true)
126+
}
127+
121128
if !loadingState.productsLoaded && dataSource.isLoadingProducts {
122-
return .loading
129+
return .loading()
123130
}
124131
return .content
125132
}
126133

134+
var isInitialCatalogSync: Bool {
135+
guard let syncState = catalogSyncCoordinator.fullSyncStateModel.state[siteID] else {
136+
return false
137+
}
138+
139+
switch syncState {
140+
case .syncStarted(_, true), .syncNeverDone:
141+
return true
142+
default:
143+
return false
144+
}
145+
}
146+
127147
var rootState: ItemListState {
128148
computeItemListState(
129149
items: dataSource.productItems,
@@ -244,3 +264,12 @@ private extension PointOfSaleObservableItemsController {
244264
var variationsLoaded = false
245265
}
246266
}
267+
268+
private extension PointOfSaleObservableItemsController {
269+
func preloadloadLastFullSyncState() {
270+
Task { @MainActor in
271+
/// Ensure last full sync state is loaded with initial value
272+
_ = await catalogSyncCoordinator.loadLastFullSyncState(for: siteID)
273+
}
274+
}
275+
}
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
import Foundation
22

33
enum ItemsContainerState {
4-
case loading
4+
case loading(isCatalogSyncing: Bool = false)
55
case error(PointOfSaleErrorState)
66
case content
7+
8+
var isCatalogSyncing: Bool {
9+
switch self {
10+
case .loading(let isCatalogSyncing):
11+
return isCatalogSyncing
12+
default:
13+
return false
14+
}
15+
}
716
}
817

918
extension ItemsContainerState: Equatable {}

Modules/Sources/PointOfSale/Models/PointOfSaleAggregateModel.swift

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import enum Yosemite.POSItemType
1515
import protocol Yosemite.PointOfSaleBarcodeScanServiceProtocol
1616
import enum Yosemite.PointOfSaleBarcodeScanError
1717
import protocol Yosemite.POSCatalogSyncCoordinatorProtocol
18-
import enum Yosemite.POSCatalogSyncState
1918
import class Yosemite.POSCatalogSyncCoordinator
2019

2120
protocol PointOfSaleAggregateModelProtocol {
@@ -56,15 +55,13 @@ protocol PointOfSaleAggregateModelProtocol {
5655
private let barcodeScanService: PointOfSaleBarcodeScanServiceProtocol
5756
private let siteID: Int64
5857
private let catalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol?
59-
var catalogSyncState: POSCatalogSyncState?
6058

6159
private var startPaymentOnCardReaderConnection: AnyCancellable?
6260
private var cardReaderDisconnection: AnyCancellable?
6361

6462
private let soundPlayer: PointOfSaleSoundPlayerProtocol
6563

6664
private var cancellables: Set<AnyCancellable> = []
67-
private var catalogSyncStateTask: Task<Void, Never>?
6865

6966
// Private storage of the concrete coordinator
7067
private let _viewStateCoordinator = PointOfSaleViewStateCoordinator()
@@ -119,7 +116,6 @@ protocol PointOfSaleAggregateModelProtocol {
119116
setupReaderReconnectionObservation()
120117
setupPaymentSuccessObservation()
121118
performIncrementalSync()
122-
setupCatalogSyncStateObservation()
123119
}
124120
}
125121

@@ -603,7 +599,6 @@ extension PointOfSaleAggregateModel {
603599
// cancelling them explicitly helps reduce the risk of user-visible bugs while we work on the memory leaks.
604600
resetCardReaderObservation()
605601
cancellables.forEach { $0.cancel() }
606-
catalogSyncStateTask?.cancel()
607602
}
608603
}
609604

@@ -630,33 +625,6 @@ private extension PointOfSaleAggregateModel {
630625
}
631626
}
632627

633-
// MARK: - Catalog Sync State Observation
634-
635-
private extension PointOfSaleAggregateModel {
636-
private func setupCatalogSyncStateObservation() {
637-
guard let coordinator = catalogSyncCoordinator else { return }
638-
639-
Task { @MainActor in
640-
catalogSyncState = await coordinator.lastFullSyncState(for: siteID)
641-
}
642-
643-
observeCatalogSyncState()
644-
}
645-
646-
@Sendable private func observeCatalogSyncState() {
647-
withObservationTracking { [weak self] in
648-
guard let self else { return }
649-
650-
if let state = catalogSyncCoordinator?.fullSyncStateModel.state[siteID] {
651-
catalogSyncState = state
652-
}
653-
} onChange: { [weak self] in
654-
guard let self else { return }
655-
DispatchQueue.main.async(execute: observeCatalogSyncState)
656-
}
657-
}
658-
}
659-
660628
#if DEBUG
661629
extension PointOfSaleAggregateModel {
662630
func setPreviewState(paymentState: PointOfSalePaymentState, inlineMessage: PointOfSaleCardPresentPaymentMessageType?) {

Modules/Sources/PointOfSale/Presentation/PointOfSaleDashboardView.swift

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,9 @@ struct PointOfSaleDashboardView: View {
4242
// MARK: View State
4343

4444
enum ViewState: Equatable {
45-
case loading
45+
case loading(isCatalogSyncing: Bool = false)
4646
case ineligible(reason: POSIneligibleReason)
4747
case error(PointOfSaleErrorState)
48-
case catalogSyncing
4948
case content
5049
case unsupportedWidth
5150
}
@@ -54,19 +53,26 @@ struct PointOfSaleDashboardView: View {
5453
PointOfSaleDashboardViewHelper.determineViewState(
5554
eligibilityState: posModel.entryPointController.eligibilityState,
5655
itemsContainerState: itemsViewState.containerState,
57-
horizontalSizeClass: horizontalSizeClass,
58-
catalogSyncState: posModel.catalogSyncState
56+
horizontalSizeClass: horizontalSizeClass
5957
)
6058
}
6159

6260
var body: some View {
6361
@Bindable var posModel = posModel
6462
ZStack(alignment: .bottomLeading) {
6563
switch viewState {
66-
case .loading:
67-
PointOfSaleLoadingView(animation: animation)
68-
.transition(.opacity)
69-
.ignoresSafeArea()
64+
case .loading(let isCatalogSyncing):
65+
if isCatalogSyncing {
66+
POSCatalogLoadingView(animation: animation) {
67+
dismiss()
68+
}
69+
.transition(.opacity)
70+
.ignoresSafeArea()
71+
} else {
72+
PointOfSaleLoadingView(animation: animation)
73+
.transition(.opacity)
74+
.ignoresSafeArea()
75+
}
7076
case .ineligible(let reason):
7177
POSIneligibleView(reason: reason, onRefresh: {
7278
try await posModel.entryPointController.refreshEligibility(reason: reason)
@@ -87,12 +93,6 @@ struct PointOfSaleDashboardView: View {
8793
}
8894
}
8995
})
90-
case .catalogSyncing:
91-
POSCatalogLoadingView(animation: animation) {
92-
dismiss()
93-
}
94-
.transition(.opacity)
95-
.ignoresSafeArea()
9696
case .content:
9797
contentView
9898
.accessibilitySortPriority(2)
@@ -118,7 +118,7 @@ struct PointOfSaleDashboardView: View {
118118
CGSizeMake(floatingSize.width + Constants.floatingControlHorizontalOffset,
119119
floatingSize.height + Constants.floatingControlVerticalOffset))
120120
.environment(\.posBackgroundAppearance, backgroundAppearance)
121-
.animation(.easeInOut, value: viewState == .loading)
121+
.animation(.easeInOut, value: viewState == .loading())
122122
.background(Color.posSurface)
123123
.navigationBarBackButtonHidden(true)
124124
.posModal(item: $posModel.cardPresentPaymentOnboardingViewContainer, onDismiss: {

Modules/Sources/PointOfSale/Utils/PreviewHelpers.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ struct PointOfSalePreviewPurchasableItemFetchStrategy: PointOfSalePurchasableIte
102102
}
103103

104104
final class PointOfSalePreviewCouponsController: PointOfSaleCouponsControllerProtocol {
105-
@Published var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading,
105+
@Published var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading(),
106106
itemsStack: ItemsStackState(root: .loading([]),
107107
itemStates: [:]))
108108
func enableCoupons() async { }
@@ -114,7 +114,7 @@ final class PointOfSalePreviewCouponsController: PointOfSaleCouponsControllerPro
114114
}
115115

116116
final class PointOfSalePreviewItemsController: PointOfSaleSearchingItemsControllerProtocol {
117-
@Published var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading,
117+
@Published var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading(),
118118
itemsStack: ItemsStackState(root: .loading([]),
119119
itemStates: [:]))
120120

@@ -632,7 +632,7 @@ final class POSPreviewCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol
632632

633633
let fullSyncStateModel = POSCatalogSyncStateModel()
634634

635-
func lastFullSyncState(for siteID: Int64) async -> POSCatalogSyncState {
635+
func loadLastFullSyncState(for siteID: Int64) async -> POSCatalogSyncState {
636636
return fullSyncStateModel.state[siteID] ?? .syncCompleted(siteID: siteID)
637637
}
638638
}

Modules/Sources/PointOfSale/ViewHelpers/PointOfSaleDashboardViewHelper.swift

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,27 @@
11
import Foundation
22
import SwiftUI
3-
import enum Yosemite.POSCatalogSyncState
43

54
struct PointOfSaleDashboardViewHelper {
65
static func determineViewState(
76
eligibilityState: POSEligibilityState?,
87
itemsContainerState: ItemsContainerState,
9-
horizontalSizeClass: UserInterfaceSizeClass?,
10-
catalogSyncState: POSCatalogSyncState?
8+
horizontalSizeClass: UserInterfaceSizeClass?
119
) -> PointOfSaleDashboardView.ViewState {
1210

13-
/// Check first to show syncing state as soon as possible
14-
if let syncState = catalogSyncState {
15-
switch syncState {
16-
case .syncStarted(_, true):
17-
return .catalogSyncing
18-
case .syncNeverDone:
19-
return .catalogSyncing
20-
case .syncStarted(_, false):
21-
// Non-initial sync, continue to other checks
22-
break
23-
case .syncCompleted:
24-
// Continue to other checks
25-
break
26-
case .syncFailed:
27-
// TODO: WOOMOB-1565
28-
return .error(PointOfSaleErrorState.errorOnLoadingOrders())
29-
}
30-
}
31-
3211
guard case .regular = horizontalSizeClass else {
3312
return .unsupportedWidth
3413
}
3514

3615
guard let eligibilityState else {
37-
return .loading
16+
return .loading(isCatalogSyncing: itemsContainerState.isCatalogSyncing)
3817
}
3918

4019
switch eligibilityState {
4120
case .eligible:
4221
// Check items container state
4322
switch itemsContainerState {
44-
case .loading:
45-
return .loading
23+
case let .loading(isCatalogSyncing):
24+
return .loading(isCatalogSyncing: isCatalogSyncing)
4625
case .error(let error):
4726
return .error(error)
4827
case .content:
@@ -59,7 +38,7 @@ extension PointOfSaleDashboardView.ViewState {
5938
switch self {
6039
case .content, .error, .unsupportedWidth:
6140
return true
62-
case .loading, .ineligible, .catalogSyncing:
41+
case .loading, .ineligible:
6342
return false
6443
}
6544
}

Modules/Sources/Yosemite/Tools/POS/POSCatalogSyncCoordinator.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public protocol POSCatalogSyncCoordinatorProtocol {
3333

3434
/// Returns the last known full sync state for a site
3535
/// If no state is cached, determines state from lastSyncDate
36-
func lastFullSyncState(for siteID: Int64) async -> POSCatalogSyncState
36+
func loadLastFullSyncState(for siteID: Int64) async -> POSCatalogSyncState
3737
}
3838

3939
public extension POSCatalogSyncCoordinatorProtocol {
@@ -300,16 +300,21 @@ public actor POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
300300
}
301301
}
302302

303-
public func lastFullSyncState(for siteID: Int64) async -> POSCatalogSyncState {
303+
public func loadLastFullSyncState(for siteID: Int64) async -> POSCatalogSyncState {
304304
if let cached = fullSyncStateModel.state[siteID] {
305305
return cached
306306
}
307307

308+
let state: POSCatalogSyncState
309+
308310
if await lastFullSyncDate(for: siteID) == nil {
309-
return .syncNeverDone(siteID: siteID)
311+
state = .syncNeverDone(siteID: siteID)
310312
} else {
311-
return .syncCompleted(siteID: siteID)
313+
state = .syncCompleted(siteID: siteID)
312314
}
315+
316+
fullSyncStateModel.state[siteID] = state
317+
return state
313318
}
314319
}
315320

@@ -333,6 +338,7 @@ public class POSCatalogSyncStateModel {
333338
public init() {}
334339
}
335340

341+
336342
public enum POSCatalogSyncState: Equatable {
337343
case syncStarted(siteID: Int64, isInitialSync: Bool)
338344
case syncCompleted(siteID: Int64)

0 commit comments

Comments
 (0)