Skip to content

Commit 31c2496

Browse files
authored
[Booking] Fix empty state after reload (#16417)
2 parents 7ca6531 + 3833147 commit 31c2496

File tree

5 files changed

+85
-24
lines changed

5 files changed

+85
-24
lines changed

Modules/Sources/Yosemite/Actions/BookingAction.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,12 @@ public enum BookingAction: Action {
103103
bookingID: Int64,
104104
note: String,
105105
onCompletion: (Error?) -> Void)
106+
107+
108+
/// Clears the booking cache.
109+
///
110+
/// - Parameter siteID: The site ID of the booking.
111+
/// - Parameter onCompletion: Called when clear completes.
112+
case clearBookingsCache(siteID: Int64,
113+
onCompletion: () -> Void)
106114
}

Modules/Sources/Yosemite/Stores/BookingStore.swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ public class BookingStore: Store {
8989
note: note,
9090
onCompletion: onCompletion
9191
)
92+
case .clearBookingsCache(siteID: let siteID, onCompletion: let onCompletion):
93+
clearBookingsCache(siteID: siteID, onCompletion: onCompletion)
9294
}
9395
}
9496
}
@@ -205,12 +207,12 @@ private extension BookingStore {
205207
/// Returns results immediately without saving to storage.
206208
///
207209
func searchBookings(siteID: Int64,
208-
searchQuery: String,
209-
pageNumber: Int,
210-
pageSize: Int,
211-
filters: BookingFilters?,
212-
order: BookingsRemote.Order,
213-
onCompletion: @escaping (Result<[Booking], Error>) -> Void) {
210+
searchQuery: String,
211+
pageNumber: Int,
212+
pageSize: Int,
213+
filters: BookingFilters?,
214+
order: BookingsRemote.Order,
215+
onCompletion: @escaping (Result<[Booking], Error>) -> Void) {
214216
Task { @MainActor in
215217
do {
216218
let bookings = try await remote.loadAllBookings(for: siteID,
@@ -271,9 +273,9 @@ private extension BookingStore {
271273
/// Synchronizes booking resources for the specified site.
272274
///
273275
func synchronizeResources(siteID: Int64,
274-
pageNumber: Int,
275-
pageSize: Int,
276-
onCompletion: @escaping (Result<Bool, Error>) -> Void) {
276+
pageNumber: Int,
277+
pageSize: Int,
278+
onCompletion: @escaping (Result<Bool, Error>) -> Void) {
277279
Task { @MainActor in
278280
do {
279281
let resources = try await remote.fetchResources(
@@ -461,6 +463,12 @@ private extension BookingStore {
461463
}
462464
}
463465
}
466+
467+
func clearBookingsCache(siteID: Int64, onCompletion: @escaping () -> Void) {
468+
storageManager.performAndSave({ storage in
469+
storage.deleteBookings(siteID: siteID)
470+
}, completion: onCompletion, on: .main)
471+
}
464472
}
465473

466474

WooCommerce/Classes/Bookings/BookingList/BookingListContainerViewModel.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ final class BookingListContainerViewModel: ObservableObject {
2424
private var searchQuerySubscription: AnyCancellable?
2525
private var sortBySubscription: AnyCancellable?
2626

27+
private lazy var allTabViewModels: [BookingListViewModel] = [
28+
todayListViewModel,
29+
upcomingListViewModel,
30+
allListViewModel
31+
]
2732

2833
private var filters = BookingFiltersViewModel.Filters()
2934

@@ -87,6 +92,10 @@ final class BookingListContainerViewModel: ObservableObject {
8792
}
8893

8994
restorePersistedFilters()
95+
96+
todayListViewModel.refreshCoordinator = self
97+
upcomingListViewModel.refreshCoordinator = self
98+
allListViewModel.refreshCoordinator = self
9099
}
91100

92101
func listViewModel(for tab: BookingListTab) -> BookingListViewModel {
@@ -181,6 +190,29 @@ private extension BookingListContainerViewModel {
181190
}
182191
}
183192

193+
extension BookingListContainerViewModel: BookingListsRefreshCoordinating {
194+
@MainActor
195+
func refreshAllLists() async {
196+
await withCheckedContinuation { continuation in
197+
let action = BookingAction.clearBookingsCache(siteID: siteID) {
198+
continuation.resume()
199+
}
200+
stores.dispatch(action)
201+
}
202+
203+
// Launch all tab refreshes in parallel and wait for all to complete
204+
await withTaskGroup(of: Void.self) { group in
205+
for viewModel in allTabViewModels {
206+
group.addTask { @MainActor in
207+
await viewModel.reloadData(
208+
reason: BookingListViewModel.siblingRefreshReason
209+
)
210+
}
211+
}
212+
}
213+
}
214+
}
215+
184216
private extension BookingListContainerViewModel {
185217
enum Localization {
186218
static let filter = NSLocalizedString(

WooCommerce/Classes/Bookings/BookingList/BookingListViewModel.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import Combine
55
import protocol Storage.StorageManagerType
66
import class Networking.BookingsRemote
77

8+
protocol BookingListsRefreshCoordinating: AnyObject {
9+
func refreshAllLists() async
10+
}
11+
812
/// View model for `BookingListView`
913
final class BookingListViewModel: ObservableObject {
1014

@@ -14,6 +18,8 @@ final class BookingListViewModel: ObservableObject {
1418

1519
@Published private(set) var hasFilters = false
1620

21+
weak var refreshCoordinator: BookingListsRefreshCoordinating?
22+
1723
var emptyStateTitle: String {
1824
type.emptyStateTitle(hasFilters: hasFilters)
1925
}
@@ -32,6 +38,7 @@ final class BookingListViewModel: ObservableObject {
3238

3339
private static let refreshCacheReason = "refresh-cache"
3440
private static let reorderReason = "reorder"
41+
static let siblingRefreshReason = "sibling-refresh"
3542

3643
/// Keeps track of the current state of the syncing
3744
@Published private(set) var syncState: SyncState = .empty
@@ -58,6 +65,7 @@ final class BookingListViewModel: ObservableObject {
5865

5966
init(siteID: Int64,
6067
type: BookingListTab,
68+
parent: BookingListContainerViewModel? = nil,
6169
stores: StoresManager = ServiceLocator.stores,
6270
storage: StorageManagerType = ServiceLocator.storageManager,
6371
currentDate: Date = Date()) {
@@ -96,10 +104,14 @@ final class BookingListViewModel: ObservableObject {
96104
}
97105

98106
/// Called when the user pulls down the list to refresh.
99-
@MainActor
100107
func onRefreshAction() async {
108+
await refreshCoordinator?.refreshAllLists()
109+
}
110+
111+
@MainActor
112+
func reloadData(reason: String = BookingListViewModel.refreshCacheReason) async {
101113
await withCheckedContinuation { continuation in
102-
paginationTracker.resync(reason: Self.refreshCacheReason) {
114+
paginationTracker.resync(reason: reason) {
103115
continuation.resume(returning: ())
104116
}
105117
}

WooCommerce/WooCommerceTests/ViewRelated/Bookings/BookingListViewModelTests.swift

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ struct BookingListViewModelTests {
307307
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, stores: stores)
308308

309309
// When
310-
await viewModel.onRefreshAction()
310+
await viewModel.reloadData()
311311

312312
// Then
313313
#expect(skip == 0)
@@ -441,26 +441,18 @@ struct BookingListViewModelTests {
441441
#expect(actionCallCount == 2, "Should have made two API calls")
442442
}
443443

444-
@Test func on_refresh_action_clears_cache() async {
444+
@Test func on_refresh_action_calls_refreshcoordiantor() async {
445445
// Given
446446
let stores = MockStoresManager(sessionManager: .testingInstance)
447-
var capturedShouldClearCache: Bool?
448-
449-
stores.whenReceivingAction(ofType: BookingAction.self) { action in
450-
guard case let .synchronizeBookings(_, _, _, _, _, shouldClearCache, onCompletion) = action else {
451-
return
452-
}
453-
capturedShouldClearCache = shouldClearCache
454-
onCompletion(.success(false))
455-
}
456-
447+
let mockRefresher = MockBookingListsRefreshCoordinating()
457448
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, stores: stores)
449+
viewModel.refreshCoordinator = mockRefresher
458450

459451
// When
460452
await viewModel.onRefreshAction()
461453

462454
// Then
463-
#expect(capturedShouldClearCache == true, "Refresh action should clear cache")
455+
#expect(mockRefresher.refreshAllListsCalled)
464456
}
465457

466458
// MARK: - Local storage filtering
@@ -719,3 +711,12 @@ private extension BookingListViewModelTests {
719711
return Booking.fake().copy(siteID: siteID ?? self.sampleSiteID, bookingID: id, startDate: startDate)
720712
}
721713
}
714+
715+
716+
class MockBookingListsRefreshCoordinating: BookingListsRefreshCoordinating {
717+
private(set) var refreshAllListsCalled = false
718+
719+
func refreshAllLists() async {
720+
refreshAllListsCalled = true
721+
}
722+
}

0 commit comments

Comments
 (0)