Skip to content

Commit 8e0c018

Browse files
authored
[Woo POS][Local Catalog] Loading screen for missing catalog when launching POS (#16272)
2 parents 64d5874 + 6f0230e commit 8e0c018

24 files changed

+350
-78
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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import enum Yosemite.POSItemType
1515
import protocol Yosemite.PointOfSaleBarcodeScanServiceProtocol
1616
import enum Yosemite.PointOfSaleBarcodeScanError
1717
import protocol Yosemite.POSCatalogSyncCoordinatorProtocol
18+
import class Yosemite.POSCatalogSyncCoordinator
1819

1920
protocol PointOfSaleAggregateModelProtocol {
2021
var cart: Cart { get }

Modules/Sources/PointOfSale/Presentation/CardReaderConnection/UI States/PointOfSaleLoadingView.swift

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,44 @@
11
import SwiftUI
22

33
struct PointOfSaleLoadingView: View {
4+
private let isCatalogSyncing: Bool
5+
private let onExit: (() -> Void)?
6+
7+
init(isCatalogSyncing: Bool = false, onExit: (() -> Void)? = nil) {
8+
self.isCatalogSyncing = isCatalogSyncing
9+
self.onExit = onExit
10+
}
11+
412
var body: some View {
513
HStack(alignment: .center) {
614
Spacer()
715
VStack(alignment: .center) {
816
Spacer()
917
ProgressView()
1018
.progressViewStyle(POSProgressViewStyle())
11-
Spacer()
19+
20+
if isCatalogSyncing {
21+
Spacer().frame(height: POSSpacing.large * 2)
22+
Text(Localization.syncingTitle)
23+
.font(.posHeadingBold)
24+
Spacer()
25+
VStack(spacing: POSSpacing.medium) {
26+
Button {
27+
onExit?()
28+
} label: {
29+
Text(Localization.exitButtonTitle)
30+
.font(.posBodySmallBold(underline: true))
31+
.foregroundStyle(Color.posOnSurface)
32+
}
33+
34+
Text(Localization.exitButtonDescription)
35+
.font(.posCaptionRegular)
36+
.foregroundStyle(Color.posOnSurfaceVariantLowest)
37+
}
38+
.padding(.bottom, POSPadding.large)
39+
} else {
40+
Spacer()
41+
}
1242
}
1343
.multilineTextAlignment(.center)
1444
Spacer()
@@ -20,3 +50,29 @@ struct PointOfSaleLoadingView: View {
2050
#Preview {
2151
PointOfSaleLoadingView()
2252
}
53+
54+
#Preview("Catalog Syncing") {
55+
PointOfSaleLoadingView(isCatalogSyncing: true) {}
56+
}
57+
58+
private extension PointOfSaleLoadingView {
59+
struct Localization {
60+
static let syncingTitle = NSLocalizedString(
61+
"pointOfSale.catalogLoadingView.title",
62+
value: "Syncing catalog",
63+
comment: "A title of a full screen view that is displayed while the POS catalog is being synced."
64+
)
65+
66+
static let exitButtonTitle = NSLocalizedString(
67+
"pointOfSale.catalogLoadingView.exitButton.title",
68+
value: "Exit POS",
69+
comment: "A button that exits POS."
70+
)
71+
72+
static let exitButtonDescription = NSLocalizedString(
73+
"pointOfSale.catalogLoadingView.exitButton.description",
74+
value: "Syncing will continue in the background.",
75+
comment: "A description within a full screen loading view for POS catalog."
76+
)
77+
}
78+
}

Modules/Sources/PointOfSale/Presentation/CouponRowView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ private extension CouponRowView {
9292
static let horizontalPadding: CGFloat = POSPadding.medium
9393
static let horizontalElementSpacing: CGFloat = POSSpacing.medium
9494
static let cardContentHorizontalPadding: CGFloat = POSPadding.medium
95-
static let titleFont: POSFontStyle = .posBodySmallBold
95+
static let titleFont: POSFontStyle = .posBodySmallBold()
9696
static let titleSummarySpacing: CGFloat = POSSpacing.xSmall
9797
static let summaryFont: POSFontStyle = .posBodySmallRegular()
9898
}

Modules/Sources/PointOfSale/Presentation/ItemRowView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ private extension ItemRowView {
131131
static let horizontalElementSpacing: CGFloat = POSSpacing.medium
132132
static let cardContentHorizontalPadding: CGFloat = POSPadding.medium
133133
static let itemTitleAndPriceSpacing: CGFloat = POSSpacing.xSmall
134-
static let itemTitleFont: POSFontStyle = .posBodySmallBold
134+
static let itemTitleFont: POSFontStyle = .posBodySmallBold()
135135
static let itemSubtitleFont: POSFontStyle = .posBodySmallRegular()
136136
static let itemPriceFont: POSFontStyle = .posBodySmallRegular()
137137
static let titleSubtitleLineLimit: Int = 4

Modules/Sources/PointOfSale/Presentation/Orders/POSOrderListView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ private struct POSOrderRowView: View {
234234
private var orderHeaderRow: some View {
235235
HStack(alignment: .center) {
236236
Text(POSOrderListView.Localization.orderTitle(order.number))
237-
.font(.posBodySmallBold)
237+
.font(.posBodySmallBold())
238238
.foregroundStyle(Color.posOnSurface)
239239
.fixedSize(horizontal: false, vertical: true)
240240

Modules/Sources/PointOfSale/Presentation/PointOfSaleDashboardView.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ struct PointOfSaleDashboardView: View {
66
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
77
@Environment(\.posAnalytics) private var analytics
88
@Environment(\.posExternalViews) private var externalViews
9+
@Environment(\.dismiss) private var dismiss
910

1011
@State private var showExitPOSModal: Bool = false
1112
@State private var showSupport: Bool = false
@@ -39,7 +40,7 @@ struct PointOfSaleDashboardView: View {
3940
// MARK: View State
4041

4142
enum ViewState: Equatable {
42-
case loading
43+
case loading(isCatalogSyncing: Bool = false)
4344
case ineligible(reason: POSIneligibleReason)
4445
case error(PointOfSaleErrorState)
4546
case content
@@ -58,8 +59,11 @@ struct PointOfSaleDashboardView: View {
5859
@Bindable var posModel = posModel
5960
ZStack(alignment: .bottomLeading) {
6061
switch viewState {
61-
case .loading:
62-
PointOfSaleLoadingView()
62+
case .loading(let isCatalogSyncing):
63+
PointOfSaleLoadingView(
64+
isCatalogSyncing: isCatalogSyncing,
65+
onExit: { dismiss() }
66+
)
6367
.transition(.opacity)
6468
.ignoresSafeArea()
6569
case .ineligible(let reason):
@@ -107,7 +111,7 @@ struct PointOfSaleDashboardView: View {
107111
CGSizeMake(floatingSize.width + Constants.floatingControlHorizontalOffset,
108112
floatingSize.height + Constants.floatingControlVerticalOffset))
109113
.environment(\.posBackgroundAppearance, backgroundAppearance)
110-
.animation(.easeInOut, value: viewState == .loading)
114+
.animation(.easeInOut, value: viewState == .loading())
111115
.background(Color.posSurface)
112116
.navigationBarBackButtonHidden(true)
113117
.posModal(item: $posModel.cardPresentPaymentOnboardingViewContainer, onDismiss: {

Modules/Sources/PointOfSale/Presentation/Reusable Views/POSConnectivityView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ struct POSConnectivityView: View {
3030
HStack(spacing: Constants.spacing) {
3131
Image(systemName: "wifi.exclamationmark")
3232
.foregroundColor(Color.posOnSecondaryContainer)
33-
.font(.posBodySmallBold)
33+
.font(.posBodySmallBold())
3434

3535
Text(Localization.title)
3636
.foregroundColor(Color.posOnSecondaryContainer)
37-
.font(.posBodySmallBold)
37+
.font(.posBodySmallBold())
3838
}
3939
.padding(.vertical, Constants.verticalPadding)
4040
.padding(.horizontal, Constants.horizontalPadding)

0 commit comments

Comments
 (0)