Skip to content

Commit 213da87

Browse files
authored
Merge pull request #4958 from woocommerce/issue/4780-country-loading-state
2 parents 2a176e0 + 20dd6ee commit 213da87

File tree

3 files changed

+63
-9
lines changed

3 files changed

+63
-9
lines changed

WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/CountrySelectorViewModel.swift

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ final class CountrySelectorViewModel: FilterListSelectorViewModelable, Observabl
1515
}
1616
}
1717

18+
/// Define if the view should show placeholders instead of the real elements.
19+
///
20+
@Published private(set) var showPlaceholders: Bool = false
21+
1822
/// Command that powers the `ListSelector` view.
1923
///
2024
let command = CountrySelectorCommand(countries: [])
@@ -34,6 +38,10 @@ final class CountrySelectorViewModel: FilterListSelectorViewModelable, Observabl
3438
return ResultsController<StorageCountry>(storageManager: storageManager, sortedBy: [countriesDescriptor])
3539
}()
3640

41+
/// Trigger to sync countries.
42+
///
43+
private let syncCountriesTrigger = PassthroughSubject<Void, Never>()
44+
3745
/// Storage to fetch countries
3846
///
3947
private let storageManager: StorageManagerType
@@ -50,15 +58,17 @@ final class CountrySelectorViewModel: FilterListSelectorViewModelable, Observabl
5058
self.siteID = siteID
5159
self.storageManager = storageManager
5260
self.stores = stores
53-
fetchAndBindCountries()
61+
bindSyncTrigger()
62+
bindStoredCountries()
5463
}
5564
}
5665

5766
// MARK: Helpers
5867
private extension CountrySelectorViewModel {
59-
/// Fetches & Binds countries from storage, If there are no stored countries, sync them from a remote source.
68+
/// Fetches & Binds countries from storage, If there are no stored countries, trigger a sync request.
6069
///
61-
func fetchAndBindCountries() {
70+
func bindStoredCountries() {
71+
6272
// Bind stored countries & command
6373
countriesResultsController.onDidChangeContent = { [weak self] in
6474
guard let self = self else { return }
@@ -68,13 +78,46 @@ private extension CountrySelectorViewModel {
6878
// Initial fetch
6979
try? countriesResultsController.performFetch()
7080

71-
// Reset countries with fetched data or sync countries if needed.
72-
if !countriesResultsController.isEmpty {
73-
command.resetCountries(countriesResultsController.fetchedObjects)
74-
} else {
75-
let action = DataAction.synchronizeCountries(siteID: siteID, onCompletion: { _ in })
76-
stores.dispatch(action)
81+
// Trigger a sync request if there are no countries.
82+
guard !countriesResultsController.isEmpty else {
83+
return syncCountriesTrigger.send()
84+
}
85+
86+
// Reset countries with fetched
87+
command.resetCountries(countriesResultsController.fetchedObjects)
88+
}
89+
90+
/// Sync countries when requested. Defines the `showPlaceholderState` value depending if countries are being synced or not.
91+
///
92+
func bindSyncTrigger() {
93+
syncCountriesTrigger
94+
.handleEvents(receiveOutput: { // Set `showPlaceholders` to `true` before initiating sync.
95+
self.showPlaceholders = true // I could not find a way to assign this using combine operators. :-(
96+
})
97+
.map { // Sync countries
98+
self.makeSyncCountriesFuture()
99+
.replaceError(with: ()) // TODO: Handle errors
100+
}
101+
.switchToLatest()
102+
.map { _ in // Set `showPlaceholders` to `false` after sync is done.
103+
false
104+
}
105+
.assign(to: &$showPlaceholders)
106+
}
107+
108+
/// Creates a publisher that syncs countries into our storage layer.
109+
///
110+
func makeSyncCountriesFuture() -> AnyPublisher<Void, Error> {
111+
Future<Void, Error> { [weak self] promise in
112+
guard let self = self else { return }
113+
114+
let action = DataAction.synchronizeCountries(siteID: self.siteID) { result in
115+
let newResult = result.map { _ in } // Hides the result success type because we don't need it.
116+
promise(newResult)
117+
}
118+
self.stores.dispatch(action)
77119
}
120+
.eraseToAnyPublisher()
78121
}
79122
}
80123

WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/FilterListSelector.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import SwiftUI
2+
import Shimmer
23

34
protocol FilterListSelectorViewModelable: ObservableObject {
45

@@ -19,6 +20,10 @@ protocol FilterListSelectorViewModelable: ObservableObject {
1920
/// Placeholder for the filter text field
2021
///
2122
var filterPlaceholder: String { get }
23+
24+
/// Value to indicate if the views should be redacted
25+
///
26+
var showPlaceholders: Bool { get }
2227
}
2328

2429
/// Filterable List Selector View
@@ -37,6 +42,8 @@ struct FilterListSelector<ViewModel: FilterListSelectorViewModelable>: View {
3742
ListSelector(command: viewModel.command, tableStyle: .plain)
3843
}
3944
.navigationTitle(viewModel.navigationTitle)
45+
.redacted(reason: viewModel.showPlaceholders ? .placeholder : [])
46+
.shimmering(active: viewModel.showPlaceholders)
4047
}
4148
}
4249

WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/StateSelectorViewModel.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ final class StateSelectorViewModel: FilterListSelectorViewModelable, ObservableO
2525
/// Filter text field placeholder
2626
///
2727
let filterPlaceholder = Localization.placeholder
28+
29+
/// States do not require data loading
30+
///
31+
let showPlaceholders = false
2832
}
2933

3034
// MARK: Constants

0 commit comments

Comments
 (0)