Skip to content

Commit 78b2a41

Browse files
authored
[Woo POS] Make POS aggregate model Observable (#15059)
2 parents ff996a8 + 8f44ba8 commit 78b2a41

13 files changed

+88
-59
lines changed

WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22
import Combine
3+
import Observation
34

45
import protocol Yosemite.POSOrderableItem
56
import protocol WooFoundation.Analytics
@@ -38,24 +39,24 @@ protocol PointOfSaleAggregateModelProtocol {
3839
}
3940

4041
@available(iOS 17.0, *)
41-
class PointOfSaleAggregateModel: ObservableObject, PointOfSaleAggregateModelProtocol {
42-
@Published private(set) var orderStage: PointOfSaleOrderStage = .building
43-
44-
@Published private(set) var cardReaderConnectionStatus: CardPresentPaymentReaderConnectionStatus = .disconnected
45-
@Published private(set) var paymentState: PointOfSalePaymentState
46-
@Published var cardPresentPaymentAlertViewModel: PointOfSaleCardPresentPaymentAlertType?
47-
@Published private(set) var cardPresentPaymentInlineMessage: PointOfSaleCardPresentPaymentMessageType?
48-
@Published var cardPresentPaymentOnboardingViewModel: CardPresentPaymentsOnboardingViewModel?
42+
@Observable final class PointOfSaleAggregateModel: PointOfSaleAggregateModelProtocol {
43+
private(set) var orderStage: PointOfSaleOrderStage = .building
44+
45+
private(set) var cardReaderConnectionStatus: CardPresentPaymentReaderConnectionStatus = .disconnected
46+
private(set) var paymentState: PointOfSalePaymentState
47+
var cardPresentPaymentAlertViewModel: PointOfSaleCardPresentPaymentAlertType?
48+
private(set) var cardPresentPaymentInlineMessage: PointOfSaleCardPresentPaymentMessageType?
49+
var cardPresentPaymentOnboardingViewModel: CardPresentPaymentsOnboardingViewModel?
4950
private var onOnboardingCancellation: (() -> Void)?
5051

51-
@Published private(set) var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading,
52-
itemsStack: ItemsStackState(root: .loading([]),
53-
itemStates: [:]))
52+
private(set) var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading,
53+
itemsStack: ItemsStackState(root: .loading([]),
54+
itemStates: [:]))
5455

55-
@Published private(set) var cart: [CartItem] = []
56+
private(set) var cart: [CartItem] = []
5657

57-
@Published private(set) var orderState: PointOfSaleOrderState = .idle
58-
@Published private var internalOrderState: PointOfSaleInternalOrderState = .idle
58+
private(set) var orderState: PointOfSaleOrderState = .idle
59+
private var internalOrderState: PointOfSaleInternalOrderState = .idle
5960

6061
private let itemsController: PointOfSaleItemsControllerProtocol
6162

@@ -90,7 +91,10 @@ class PointOfSaleAggregateModel: ObservableObject, PointOfSaleAggregateModelProt
9091
@available(iOS 17.0, *)
9192
extension PointOfSaleAggregateModel {
9293
private func publishItemsViewState() {
93-
itemsController.itemsViewStatePublisher.assign(to: &$itemsViewState)
94+
itemsController.itemsViewStatePublisher.sink { [weak self] state in
95+
self?.itemsViewState = state
96+
}
97+
.store(in: &cancellables)
9498
}
9599

96100
@MainActor
@@ -155,8 +159,11 @@ extension PointOfSaleAggregateModel {
155159
@available(iOS 17.0, *)
156160
extension PointOfSaleAggregateModel {
157161
private func publishCardReaderConnectionStatus() {
158-
// When adopting Observable, we can use `assign(to: on:)` here instead
159-
cardPresentPaymentService.readerConnectionStatusPublisher.assign(to: &$cardReaderConnectionStatus)
162+
cardPresentPaymentService.readerConnectionStatusPublisher
163+
.sink(receiveValue: { [weak self] connectionStatus in
164+
self?.cardReaderConnectionStatus = connectionStatus
165+
})
166+
.store(in: &cancellables)
160167
}
161168

162169
func connectCardReader() {
@@ -177,7 +184,7 @@ extension PointOfSaleAggregateModel {
177184
/// e.g. when the TotalsView goes offscreen.
178185
private func startPaymentWhenCardReaderConnected() async {
179186
guard case .connected = cardReaderConnectionStatus else {
180-
return startPaymentOnCardReaderConnection = $cardReaderConnectionStatus
187+
return startPaymentOnCardReaderConnection = cardPresentPaymentService.readerConnectionStatusPublisher
181188
.filter { status in
182189
switch status {
183190
case .connected:
@@ -259,17 +266,19 @@ extension PointOfSaleAggregateModel {
259266
await collectCardPayment()
260267
}
261268

262-
private func setupReaderReconnectionObservation() {
263-
$orderStage.sink(receiveValue: { [weak self] stage in
269+
@Sendable private func setupReaderReconnectionObservation() {
270+
withObservationTracking { [weak self] in
264271
guard let self else { return }
265-
switch stage {
266-
case .building:
267-
cancelCardReaderPreparation()
268-
case .finalizing:
269-
observeReaderReconnection()
272+
switch orderStage {
273+
case .building:
274+
cancelCardReaderPreparation()
275+
case .finalizing:
276+
observeReaderReconnection()
270277
}
271-
})
272-
.store(in: &cancellables)
278+
} onChange: { [weak self] in
279+
guard let self else { return }
280+
DispatchQueue.main.async(execute: setupReaderReconnectionObservation)
281+
}
273282
}
274283

275284
private func cancelCardReaderPreparation() {
@@ -279,7 +288,7 @@ extension PointOfSaleAggregateModel {
279288
}
280289

281290
private func observeReaderReconnection() {
282-
cardReaderDisconnection = $cardReaderConnectionStatus
291+
cardReaderDisconnection = cardPresentPaymentService.readerConnectionStatusPublisher
283292
.filter({ $0 == .disconnected })
284293
.sink { [weak self] _ in
285294
Task { @MainActor [weak self] in
@@ -321,13 +330,19 @@ private extension PointOfSaleAggregateModel {
321330
}
322331
return alertType
323332
}
324-
.assign(to: &$cardPresentPaymentAlertViewModel)
333+
.sink(receiveValue: { [weak self] alertType in
334+
self?.cardPresentPaymentAlertViewModel = alertType
335+
})
336+
.store(in: &cancellables)
325337

326338
cardPresentPaymentService.paymentEventPublisher
327339
.map { [weak self] event -> PointOfSaleCardPresentPaymentMessageType? in
328340
self?.mapCardPresentPaymentEventToMessageType(event)
329341
}
330-
.assign(to: &$cardPresentPaymentInlineMessage)
342+
.sink(receiveValue: { [weak self] message in
343+
self?.cardPresentPaymentInlineMessage = message
344+
})
345+
.store(in: &cancellables)
331346

332347
cardPresentPaymentService.paymentEventPublisher
333348
.compactMap { [weak self] paymentEvent -> PointOfSalePaymentState? in
@@ -338,7 +353,10 @@ private extension PointOfSaleAggregateModel {
338353

339354
return newPaymentState
340355
}
341-
.assign(to: &$paymentState)
356+
.sink(receiveValue: { [weak self] paymentState in
357+
self?.paymentState = paymentState
358+
})
359+
.store(in: &cancellables)
342360

343361
cardPresentPaymentService.paymentEventPublisher
344362
.map { [weak self] event -> CardPresentPaymentsOnboardingViewModel? in
@@ -349,7 +367,10 @@ private extension PointOfSaleAggregateModel {
349367
onOnboardingCancellation = onCancel
350368
return viewModel
351369
}
352-
.assign(to: &$cardPresentPaymentOnboardingViewModel)
370+
.sink(receiveValue: { [weak self] onboardingViewModel in
371+
self?.cardPresentPaymentOnboardingViewModel = onboardingViewModel
372+
})
373+
.store(in: &cancellables)
353374
}
354375

355376
/// Maps PaymentEvent to POSMessageType and annonates additional information if necessary
@@ -413,9 +434,16 @@ extension PointOfSaleAggregateModel {
413434
func publishOrderState() {
414435
orderController.orderStatePublisher
415436
.map { $0.externalState }
416-
.assign(to: &$orderState)
437+
.sink(receiveValue: { [weak self] orderState in
438+
self?.orderState = orderState
439+
})
440+
.store(in: &cancellables)
417441

418-
orderController.orderStatePublisher.assign(to: &$internalOrderState)
442+
orderController.orderStatePublisher
443+
.sink(receiveValue: { [weak self] internalOrderState in
444+
self?.internalOrderState = internalOrderState
445+
})
446+
.store(in: &cancellables)
419447
}
420448
}
421449

WooCommerce/Classes/POS/Presentation/CardReaderConnection/CardReaderConnectionStatusView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SwiftUI
33
@available(iOS 17.0, *)
44
struct CardReaderConnectionStatusView: View {
55
@Environment(\.posBackgroundAppearance) var backgroundAppearance
6-
@EnvironmentObject var posModel: PointOfSaleAggregateModel
6+
@Environment(PointOfSaleAggregateModel.self) private var posModel
77
@ScaledMetric private var scale: CGFloat = 1.0
88
@Environment(\.isEnabled) var isEnabled
99

WooCommerce/Classes/POS/Presentation/CartView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import SwiftUI
22

33
@available(iOS 17.0, *)
44
struct CartView: View {
5-
@EnvironmentObject private var posModel: PointOfSaleAggregateModel
5+
@Environment(PointOfSaleAggregateModel.self) private var posModel
66
private let viewHelper = CartViewHelper()
77

88
@Environment(\.floatingControlAreaSize) var floatingControlAreaSize: CGSize
@@ -311,6 +311,6 @@ private extension CartView {
311311
cardPresentPaymentService: CardPresentPaymentPreviewService(),
312312
orderController: PointOfSalePreviewOrderController())
313313
return CartView()
314-
.environmentObject(posModel)
314+
.environment(posModel)
315315
}
316316
#endif

WooCommerce/Classes/POS/Presentation/Item Selector/ChildItemList.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Yosemite
66
struct ChildItemList: View {
77
private let parentItem: POSItem
88
private let title: String
9-
@EnvironmentObject private var posModel: PointOfSaleAggregateModel
9+
@Environment(PointOfSaleAggregateModel.self) private var posModel
1010
@Environment(\.dismiss) private var dismiss
1111

1212
private var state: ItemListState {
@@ -167,7 +167,7 @@ private extension ChildItemList {
167167
cardPresentPaymentService: CardPresentPaymentPreviewService(),
168168
orderController: PointOfSalePreviewOrderController())
169169
return ChildItemList(parentItem: parentItem, title: parentProduct.name)
170-
.environmentObject(posModel)
170+
.environment(posModel)
171171
}
172172

173173
@available(iOS 17.0, *)
@@ -191,7 +191,7 @@ private extension ChildItemList {
191191
cardPresentPaymentService: CardPresentPaymentPreviewService(),
192192
orderController: PointOfSalePreviewOrderController())
193193
return ChildItemList(parentItem: parentItem, title: parentProduct.name)
194-
.environmentObject(posModel)
194+
.environment(posModel)
195195
}
196196

197197
#endif

WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import struct Yosemite.POSVariableParentProduct
77
@available(iOS 17.0, *)
88
struct ItemList<HeaderView: View>: View {
99
@Environment(\.floatingControlAreaSize) private var floatingControlAreaSize: CGSize
10-
@EnvironmentObject var posModel: PointOfSaleAggregateModel
10+
@Environment(PointOfSaleAggregateModel.self) private var posModel
1111
@StateObject private var infiniteScrollTriggerDeterminer = ThresholdInfiniteScrollTriggerDeterminer()
1212

1313
let state: ItemListState
@@ -80,7 +80,7 @@ private enum Constants {
8080
private struct ItemListRow: View {
8181
let item: POSItem
8282
let analytics: Analytics = ServiceLocator.analytics
83-
@EnvironmentObject var posModel: PointOfSaleAggregateModel
83+
@Environment(PointOfSaleAggregateModel.self) private var posModel
8484

8585
var body: some View {
8686
switch item {
@@ -156,7 +156,7 @@ private extension ItemListRow {
156156
cardPresentPaymentService: CardPresentPaymentPreviewService(),
157157
orderController: PointOfSalePreviewOrderController())
158158
ItemList(state: .loading([]))
159-
.environmentObject(posModel)
159+
.environment(posModel)
160160
}
161161

162162
#endif

WooCommerce/Classes/POS/Presentation/ItemListView.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import protocol Yosemite.POSOrderableItem
66
struct ItemListView: View {
77
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
88

9-
@EnvironmentObject var posModel: PointOfSaleAggregateModel
9+
@Environment(PointOfSaleAggregateModel.self) private var posModel
1010

1111
@State private var showSimpleProductsModal: Bool = false
1212
private var itemListState: ItemListState {
@@ -323,7 +323,7 @@ private extension ItemListView {
323323
cardPresentPaymentService: CardPresentPaymentPreviewService(),
324324
orderController: PointOfSalePreviewOrderController())
325325
return ItemListView()
326-
.environmentObject(posModel)
326+
.environment(posModel)
327327
}
328328

329329
@available(iOS 17.0, *)
@@ -333,7 +333,7 @@ private extension ItemListView {
333333
cardPresentPaymentService: CardPresentPaymentPreviewService(),
334334
orderController: PointOfSalePreviewOrderController())
335335
return ItemListView()
336-
.environmentObject(posModel)
336+
.environment(posModel)
337337
}
338338

339339
#endif

WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SwiftUI
33
@available(iOS 17.0, *)
44
struct POSFloatingControlView: View {
55
@Environment(\.posBackgroundAppearance) var backgroundAppearance
6-
@EnvironmentObject private var posModel: PointOfSaleAggregateModel
6+
@Environment(PointOfSaleAggregateModel.self) private var posModel
77
@Environment(\.colorScheme) var colorScheme
88
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
99
@Binding private var showExitPOSModal: Bool

WooCommerce/Classes/POS/Presentation/PaymentButtons.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import SwiftUI
22

33
@available(iOS 17.0, *)
44
struct PaymentsActionButtons: View {
5-
@EnvironmentObject private var posModel: PointOfSaleAggregateModel
5+
@Environment(PointOfSaleAggregateModel.self) private var posModel
66
@Binding var isShowingSendReceiptView: Bool
77
@Binding private(set) var isShowingReceiptNotEligibleBanner: Bool
88

@@ -112,6 +112,6 @@ private extension PaymentsActionButtons {
112112
cardPresentPaymentService: CardPresentPaymentPreviewService(),
113113
orderController: PointOfSalePreviewOrderController())
114114
PaymentsActionButtons(isShowingSendReceiptView: .constant(false), isShowingReceiptNotEligibleBanner: .constant(true))
115-
.environmentObject(posModel)
115+
.environment(posModel)
116116
}
117117
#endif

WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import SwiftUI
44
struct PointOfSaleCollectCashView: View {
55
@Environment(\.colorScheme) var colorScheme
66
@Environment(\.dynamicTypeSize) var dynamicTypeSize
7-
@EnvironmentObject private var posModel: PointOfSaleAggregateModel
7+
@Environment(PointOfSaleAggregateModel.self) private var posModel
88
@FocusState private var isTextFieldFocused: Bool
99

1010
private let viewHelper = CollectCashViewHelper()
@@ -230,6 +230,6 @@ private extension PointOfSaleCollectCashView {
230230
cardPresentPaymentService: CardPresentPaymentPreviewService(),
231231
orderController: PointOfSalePreviewOrderController())
232232
PointOfSaleCollectCashView(orderTotal: "$1.23")
233-
.environmentObject(posModel)
233+
.environment(posModel)
234234
}
235235
#endif

WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import SwiftUI
22

33
@available(iOS 17.0, *)
44
struct PointOfSaleDashboardView: View {
5-
@EnvironmentObject private var posModel: PointOfSaleAggregateModel
5+
@Environment(PointOfSaleAggregateModel.self) private var posModel
66
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
77

88
@State private var showExitPOSModal: Bool = false
@@ -11,6 +11,7 @@ struct PointOfSaleDashboardView: View {
1111
@State private var floatingSize: CGSize = .zero
1212

1313
var body: some View {
14+
@Bindable var posModel = posModel
1415
ZStack(alignment: .bottomLeading) {
1516
if case .regular = horizontalSizeClass {
1617
switch posModel.itemsViewState.containerState {

0 commit comments

Comments
 (0)