Skip to content

Commit d999e5c

Browse files
joshhealdstaskus
authored andcommitted
Add PointOfSaleObservableItemsController
Creates a new controller that wraps `POSObservableDataSourceProtocol` for GRDB-based item loading. Key features: - Conforms to `PointOfSaleItemsControllerProtocol` (same interface as existing controller) - Computed `itemsViewState` from observable data source properties - Automatic UI updates via observation (no manual state management) - Supports products and variations with `.initial` state - Tracks current parent for variation state mapping Implementation notes: - No pagination tracking needed (data source handles it) - No search support (out of scope for this PR)
1 parent 6b373a5 commit d999e5c

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import Foundation
2+
import Observation
3+
import class WooFoundation.CurrencySettings
4+
import enum Yosemite.POSItem
5+
import protocol Yosemite.POSObservableDataSourceProtocol
6+
import struct Yosemite.POSVariableParentProduct
7+
import class Yosemite.GRDBObservableDataSource
8+
import protocol Storage.GRDBManagerProtocol
9+
10+
/// Controller that wraps an observable data source for POS items
11+
/// Uses computed state based on data source observations for automatic UI updates
12+
@Observable
13+
final class PointOfSaleObservableItemsController: PointOfSaleItemsControllerProtocol {
14+
private let dataSource: POSObservableDataSourceProtocol
15+
16+
// Track which items have been loaded at least once
17+
private var hasLoadedProducts = false
18+
private var hasLoadedVariationsForCurrentParent = false
19+
20+
// Track current parent for variation state mapping
21+
private var currentParentItem: POSItem?
22+
23+
var itemsViewState: ItemsViewState {
24+
ItemsViewState(
25+
containerState: containerState,
26+
itemsStack: ItemsStackState(
27+
root: rootState,
28+
itemStates: variationStates
29+
)
30+
)
31+
}
32+
33+
init(siteID: Int64,
34+
grdbManager: GRDBManagerProtocol,
35+
currencySettings: CurrencySettings) {
36+
self.dataSource = GRDBObservableDataSource(
37+
siteID: siteID,
38+
grdbManager: grdbManager,
39+
currencySettings: currencySettings
40+
)
41+
}
42+
43+
init(dataSource: POSObservableDataSourceProtocol) {
44+
self.dataSource = dataSource
45+
}
46+
47+
func loadItems(base: ItemListBaseItem) async {
48+
switch base {
49+
case .root:
50+
hasLoadedProducts = true
51+
dataSource.loadProducts()
52+
case .parent(let parent):
53+
guard case .variableParentProduct(let parentProduct) = parent else {
54+
assertionFailure("Unsupported parent type for loading items: \(parent)")
55+
return
56+
}
57+
58+
// If switching to a different parent, reset the loaded flag
59+
if currentParentItem != parent {
60+
currentParentItem = parent
61+
hasLoadedVariationsForCurrentParent = false
62+
}
63+
64+
hasLoadedVariationsForCurrentParent = true
65+
dataSource.loadVariations(for: parentProduct)
66+
}
67+
}
68+
69+
func refreshItems(base: ItemListBaseItem) async {
70+
switch base {
71+
case .root:
72+
dataSource.refresh()
73+
case .parent(let parent):
74+
guard case .variableParentProduct(let parentProduct) = parent else {
75+
assertionFailure("Unsupported parent type for refreshing items: \(parent)")
76+
return
77+
}
78+
dataSource.loadVariations(for: parentProduct)
79+
}
80+
}
81+
82+
func loadNextItems(base: ItemListBaseItem) async {
83+
switch base {
84+
case .root:
85+
dataSource.loadMoreProducts()
86+
case .parent:
87+
dataSource.loadMoreVariations()
88+
}
89+
}
90+
}
91+
92+
// MARK: - State Computation
93+
private extension PointOfSaleObservableItemsController {
94+
var containerState: ItemsContainerState {
95+
// Use .loading during initial load, .content otherwise
96+
if !hasLoadedProducts && dataSource.isLoadingProducts {
97+
return .loading
98+
}
99+
return .content
100+
}
101+
102+
var rootState: ItemListState {
103+
let items = dataSource.productItems
104+
105+
// Initial state - not yet loaded
106+
if !hasLoadedProducts {
107+
return .initial
108+
}
109+
110+
// Loading state - preserve existing items
111+
if dataSource.isLoadingProducts {
112+
return .loading(items)
113+
}
114+
115+
// Error state
116+
if let error = dataSource.error, items.isEmpty {
117+
return .error(.errorOnLoadingProducts(error: error))
118+
}
119+
120+
// Empty state
121+
if items.isEmpty {
122+
return .empty
123+
}
124+
125+
// Loaded state
126+
return .loaded(items, hasMoreItems: dataSource.hasMoreProducts)
127+
}
128+
129+
var variationStates: [POSItem: ItemListState] {
130+
guard let parentItem = currentParentItem else {
131+
return [:]
132+
}
133+
134+
let items = dataSource.variationItems
135+
136+
// Initial state - not yet loaded
137+
if !hasLoadedVariationsForCurrentParent {
138+
return [parentItem: .initial]
139+
}
140+
141+
// Loading state - preserve existing items
142+
if dataSource.isLoadingVariations {
143+
return [parentItem: .loading(items)]
144+
}
145+
146+
// Error state
147+
if let error = dataSource.error, items.isEmpty {
148+
return [parentItem: .error(.errorOnLoadingVariations(error: error))]
149+
}
150+
151+
// Empty state
152+
if items.isEmpty {
153+
return [parentItem: .empty]
154+
}
155+
156+
// Loaded state
157+
return [parentItem: .loaded(items, hasMoreItems: dataSource.hasMoreVariations)]
158+
}
159+
}

0 commit comments

Comments
 (0)