-
Notifications
You must be signed in to change notification settings - Fork 116
/
Copy pathPointOfSaleCouponsController.swift
146 lines (129 loc) · 5.27 KB
/
PointOfSaleCouponsController.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import Observation
import enum Yosemite.POSItem
import enum Yosemite.PointOfSaleCouponServiceError
import protocol Yosemite.PointOfSaleItemServiceProtocol
import protocol Yosemite.PointOfSaleCouponServiceProtocol
@available(iOS 17.0, *)
protocol PointOfSaleCouponsControllerProtocol: PointOfSaleItemsControllerProtocol {
/// Enables coupons in store settings
/// Returns true if coupons enabled
func enableCoupons() async
}
@available(iOS 17.0, *)
@Observable final class PointOfSaleCouponsController: PointOfSaleCouponsControllerProtocol {
var itemsViewState: ItemsViewState = ItemsViewState(containerState: .content,
itemsStack: ItemsStackState(root: .loading([]),
itemStates: [:]))
private let paginationTracker: AsyncPaginationTracker
private var childPaginationTrackers: [POSItem: AsyncPaginationTracker] = [:]
private let couponProvider: PointOfSaleCouponServiceProtocol
init(itemProvider: PointOfSaleCouponServiceProtocol) {
self.couponProvider = itemProvider
self.paginationTracker = .init()
}
@MainActor
func loadItems(base: ItemListBaseItem) async {
// TODO:
// Handle unhappy path:
// Depending on the error type (failed to load vs coupons disabled) we want to show a different CTA choice
await loadFirstPage()
}
@MainActor
func refreshItems(base: ItemListBaseItem) async {
// TODO:
// Handle unhappy path
await loadFirstPage()
}
@MainActor
func loadNextItems(base: ItemListBaseItem) async {
guard paginationTracker.hasNextPage else {
return
}
do {
_ = try await paginationTracker.ensureNextPageIsSynced { [weak self] pageNumber in
guard let self else { return true }
return try await fetchCoupons(pageNumber: pageNumber)
}
} catch {
// TODO: Error handling
debugPrint(error)
}
}
@MainActor
func enableCoupons() async {
itemsViewState.itemsStack.root = .loading([])
do {
try await couponProvider.enableCoupons()
await loadItems(base: .root)
} catch {
if let couponError = error as? PointOfSaleCouponServiceError {
setCouponsErrorViewState(couponError)
}
}
}
}
@available(iOS 17.0, *)
private extension PointOfSaleCouponsController {
/// Loads the first page by attempting to load the first page from local storage
/// then syncs from the remote regardless the result
func loadFirstPage() async {
do {
let storedCoupons = try await couponProvider.provideLocalPointOfSaleCoupons()
if !storedCoupons.isEmpty {
setCouponsLoadedViewState(storedCoupons, hasMoreItems: true)
}
_ = try await fetchCoupons(pageNumber: 1)
} catch {
if let couponError = error as? PointOfSaleCouponServiceError {
setCouponsErrorViewState(couponError)
}
}
}
@MainActor
func fetchCoupons(pageNumber: Int) async throws -> Bool {
do {
let pagedCoupons = try await couponProvider.providePointOfSaleCoupons(pageNumber: pageNumber)
let allCoupons = pagedCoupons.items
let hasMoreItems = pagedCoupons.hasMorePages
if allCoupons.isEmpty {
setCouponsEmptyViewState()
} else {
setCouponsLoadedViewState(allCoupons, hasMoreItems: hasMoreItems)
}
return pagedCoupons.hasMorePages
} catch {
if let couponError = error as? PointOfSaleCouponServiceError {
setCouponsErrorViewState(couponError)
}
return true
}
}
}
// MARK: - View state helpers
//
@available(iOS 17.0, *)
private extension PointOfSaleCouponsController {
func setCouponsEmptyViewState() {
let containerState = ItemsContainerState.content
let stackState = ItemsStackState(root: .empty, itemStates: [:])
itemsViewState = ItemsViewState(containerState: containerState, itemsStack: stackState)
}
func setCouponsLoadedViewState(_ coupons: [POSItem], hasMoreItems: Bool) {
itemsViewState = ItemsViewState(containerState: .content,
itemsStack: .init(root: .loaded(coupons, hasMoreItems: hasMoreItems),
itemStates: [:]))
}
func setCouponsErrorViewState(_ couponError: PointOfSaleCouponServiceError) {
let containerState = ItemsContainerState.content
let stackState: ItemsStackState
switch couponError {
case .couponsLoadingError:
stackState = ItemsStackState(root: .error(.errorOnLoadingCoupons), itemStates: [:])
case .couponsDisabled:
stackState = ItemsStackState(root: .error(.errorCouponsDisabled), itemStates: [:])
case .couponsEnablingError:
stackState = ItemsStackState(root: .error(.errorOnEnablingCoupons), itemStates: [:])
}
itemsViewState = ItemsViewState(containerState: containerState, itemsStack: stackState)
}
}