Skip to content

Commit ab84ef8

Browse files
committed
Add BookingResourceListViewModel
1 parent 6a6d0d0 commit ab84ef8

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import Foundation
2+
import Yosemite
3+
import protocol Storage.StorageManagerType
4+
5+
// View model for `BookingResourceListView`
6+
final class BookingResourceListViewModel: ObservableObject {
7+
@Published private(set) var resources: [BookingResource] = []
8+
9+
/// Keeps track of the current state of the syncing
10+
@Published private(set) var syncState: SyncState = .empty
11+
12+
/// Tracks if the infinite scroll indicator should be displayed.
13+
@Published private(set) var shouldShowBottomActivityIndicator = false
14+
15+
private let siteID: Int64
16+
private let stores: StoresManager
17+
private let storage: StorageManagerType
18+
19+
/// Supports infinite scroll.
20+
private let paginationTracker: PaginationTracker
21+
private let pageFirstIndex: Int = PaginationTracker.Defaults.pageFirstIndex
22+
23+
/// BookingResource ResultsController.
24+
private lazy var resultsController: ResultsController<StorageBookingResource> = {
25+
let predicate = NSPredicate(format: "siteID == %lld", siteID)
26+
let sortDescriptor = NSSortDescriptor(key: "resourceID", ascending: false)
27+
let resultsController = ResultsController<StorageBookingResource>(storageManager: storage,
28+
matching: predicate,
29+
sortedBy: [sortDescriptor])
30+
return resultsController
31+
}()
32+
33+
init(siteID: Int64,
34+
stores: StoresManager = ServiceLocator.stores,
35+
storage: StorageManagerType = ServiceLocator.storageManager) {
36+
self.siteID = siteID
37+
self.stores = stores
38+
self.storage = storage
39+
self.paginationTracker = PaginationTracker(pageFirstIndex: pageFirstIndex)
40+
41+
configureResultsController()
42+
configurePaginationTracker()
43+
}
44+
45+
/// Called when loading the first page of resources.
46+
func loadResources() {
47+
paginationTracker.syncFirstPage()
48+
}
49+
50+
/// Called when the next page should be loaded.
51+
func onLoadNextPageAction() {
52+
paginationTracker.ensureNextPageIsSynced()
53+
}
54+
}
55+
56+
private extension BookingResourceListViewModel {
57+
func configurePaginationTracker() {
58+
paginationTracker.delegate = self
59+
}
60+
61+
/// Performs initial fetch from storage and updates results.
62+
func configureResultsController() {
63+
resultsController.onDidChangeContent = { [weak self] in
64+
self?.updateResults()
65+
}
66+
resultsController.onDidResetContent = { [weak self] in
67+
self?.updateResults()
68+
}
69+
do {
70+
try resultsController.performFetch()
71+
updateResults()
72+
} catch {
73+
ServiceLocator.crashLogging.logError(error)
74+
}
75+
}
76+
77+
/// Updates row view models and sync state.
78+
func updateResults() {
79+
resources = resultsController.fetchedObjects
80+
transitionToResultsUpdatedState()
81+
}
82+
}
83+
84+
extension BookingResourceListViewModel: PaginationTrackerDelegate {
85+
func sync(pageNumber: Int, pageSize: Int, reason: String?, onCompletion: SyncCompletion?) {
86+
transitionToSyncingState()
87+
let action = BookingAction.synchronizeResources(
88+
siteID: siteID,
89+
pageNumber: pageNumber,
90+
pageSize: pageSize
91+
) { [weak self] result in
92+
switch result {
93+
case .success(let hasNextPage):
94+
onCompletion?(.success(hasNextPage))
95+
96+
case .failure(let error):
97+
DDLogError("⛔️ Error synchronizing bookings: \(error)")
98+
onCompletion?(.failure(error))
99+
}
100+
101+
self?.updateResults()
102+
}
103+
stores.dispatch(action)
104+
}
105+
}
106+
107+
// MARK: State Machine
108+
109+
extension BookingResourceListViewModel {
110+
/// Represents possible states for syncing bookings.
111+
enum SyncState: Equatable {
112+
case syncingFirstPage
113+
case results
114+
case empty
115+
}
116+
117+
/// Update states for sync from remote.
118+
func transitionToSyncingState() {
119+
shouldShowBottomActivityIndicator = true
120+
if resources.isEmpty {
121+
syncState = .syncingFirstPage
122+
}
123+
}
124+
125+
/// Update states after sync is complete.
126+
func transitionToResultsUpdatedState() {
127+
shouldShowBottomActivityIndicator = false
128+
syncState = resources.isNotEmpty ? .results : .empty
129+
}
130+
}

0 commit comments

Comments
 (0)