Skip to content

Commit 0f392f1

Browse files
committed
Refactor visitor and conversion stats view states to observables in the view model.
1 parent 1010e4a commit 0f392f1

File tree

3 files changed

+296
-62
lines changed

3 files changed

+296
-62
lines changed

WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsPeriodViewModel.swift

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ final class StoreStatsPeriodViewModel {
1515
/// Updated externally from user interactions with the chart.
1616
@Published var selectedIntervalIndex: Int? = nil
1717

18+
/// Updated externally from visitor stats availability.
19+
@Published var siteVisitStatsMode: SiteVisitStatsMode = .default
20+
1821
/// Emits order stats text values based on order stats and selected time interval.
1922
private(set) lazy var orderStatsText: AnyPublisher<String, Never> =
2023
Publishers.CombineLatest($orderStatsData.eraseToAnyPublisher(), $selectedIntervalIndex.eraseToAnyPublisher())
@@ -64,6 +67,24 @@ final class StoreStatsPeriodViewModel {
6467
shouldReloadChartAnimated.eraseToAnyPublisher()
6568
}
6669

70+
/// Emits the view state for visitor stats based on the visitor stats mode and the selected time interval.
71+
private(set) lazy var visitorStatsViewState: AnyPublisher<StoreStatsDataOrRedactedView.State, Never> =
72+
Publishers.CombineLatest($siteVisitStatsMode.eraseToAnyPublisher(), $selectedIntervalIndex.eraseToAnyPublisher())
73+
.compactMap { [weak self] siteVisitStatsMode, selectedIntervalIndex in
74+
return self?.visitorStatsViewState(siteVisitStatsMode: siteVisitStatsMode, selectedIntervalIndex: selectedIntervalIndex)
75+
}
76+
.removeDuplicates()
77+
.eraseToAnyPublisher()
78+
79+
/// Emits the view state for conversion stats based on the visitor stats view state.
80+
private(set) lazy var conversionStatsViewState: AnyPublisher<StoreStatsDataOrRedactedView.State, Never> =
81+
visitorStatsViewState
82+
.compactMap { [weak self] visitorStatsViewState in
83+
return self?.conversionStatsViewState(visitorStatsViewState: visitorStatsViewState)
84+
}
85+
.removeDuplicates()
86+
.eraseToAnyPublisher()
87+
6788
// MARK: - Private data
6889

6990
@Published private var siteStats: SiteVisitStats?
@@ -172,17 +193,41 @@ private extension StoreStatsPeriodViewModel {
172193
func createConversionStats(orderStatsData: OrderStatsData, siteStats: SiteVisitStats?, selectedIntervalIndex: Int?) -> String {
173194
let visitors = visitorCount(at: selectedIntervalIndex, siteStats: siteStats)
174195
let orders = orderCount(at: selectedIntervalIndex, orderStats: orderStatsData.stats, orderStatsIntervals: orderStatsData.intervals)
175-
if let visitors = visitors, let orders = orders, visitors > 0 {
196+
197+
let numberFormatter = NumberFormatter()
198+
numberFormatter.numberStyle = .percent
199+
numberFormatter.minimumFractionDigits = 1
200+
201+
if let visitors = visitors, let orders = orders {
176202
// Maximum conversion rate is 100%.
177-
let conversionRate = min(orders/visitors, 1)
178-
let numberFormatter = NumberFormatter()
179-
numberFormatter.numberStyle = .percent
180-
numberFormatter.minimumFractionDigits = 1
203+
let conversionRate = visitors > 0 ? min(orders/visitors, 1): 0
181204
return numberFormatter.string(from: conversionRate as NSNumber) ?? Constants.placeholderText
182205
} else {
183206
return Constants.placeholderText
184207
}
185208
}
209+
210+
func visitorStatsViewState(siteVisitStatsMode: SiteVisitStatsMode, selectedIntervalIndex: Int?) -> StoreStatsDataOrRedactedView.State {
211+
switch siteVisitStatsMode {
212+
case .default:
213+
return timeRange == .today && selectedIntervalIndex != nil ? .redacted: .data
214+
case .redactedDueToJetpack:
215+
return .redactedDueToJetpack
216+
case .hidden:
217+
return .redacted
218+
}
219+
}
220+
221+
func conversionStatsViewState(visitorStatsViewState: StoreStatsDataOrRedactedView.State) -> StoreStatsDataOrRedactedView.State {
222+
switch visitorStatsViewState {
223+
case .data:
224+
return .data
225+
case .redactedDueToJetpack:
226+
return .redacted
227+
case .redacted:
228+
return .redacted
229+
}
230+
}
186231
}
187232

188233
// MARK: - Private data helpers

WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsV4PeriodViewController.swift

Lines changed: 19 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ final class StoreStatsV4PeriodViewController: UIViewController {
2222

2323
var siteVisitStatsMode: SiteVisitStatsMode = .default {
2424
didSet {
25-
updateSiteVisitStats(mode: siteVisitStatsMode)
25+
viewModel.siteVisitStatsMode = siteVisitStatsMode
2626
}
2727
}
2828

@@ -154,6 +154,8 @@ final class StoreStatsV4PeriodViewController: UIViewController {
154154
observeSelectedBarIndex()
155155
observeTimeRangeBarViewModel()
156156
observeReloadChartAnimated()
157+
observeVisitorStatsViewState()
158+
observeConversionStatsViewState()
157159
}
158160

159161
override func viewWillAppear(_ animated: Bool) {
@@ -204,8 +206,6 @@ private extension StoreStatsV4PeriodViewController {
204206
self.visitorsDataOrRedactedView.isHighlighted = isHighlighted
205207
self.conversionDataOrRedactedView.isHighlighted = isHighlighted
206208
self.revenueData.textColor = textColor
207-
208-
self.updateSiteVisitStatsAndConversionRate(selectedIndex: selectedIndex)
209209
}.store(in: &cancellables)
210210
}
211211

@@ -220,6 +220,22 @@ private extension StoreStatsV4PeriodViewController {
220220
self?.reloadChart(animateChart: animated)
221221
}.store(in: &cancellables)
222222
}
223+
224+
func observeVisitorStatsViewState() {
225+
viewModel.visitorStatsViewState
226+
.sink { [weak self] viewState in
227+
guard let self = self, self.visitorsDataOrRedactedView != nil else { return }
228+
self.visitorsDataOrRedactedView.state = viewState
229+
}.store(in: &cancellables)
230+
}
231+
232+
func observeConversionStatsViewState() {
233+
viewModel.conversionStatsViewState
234+
.sink { [weak self] viewState in
235+
guard let self = self, self.conversionDataOrRedactedView != nil else { return }
236+
self.conversionDataOrRedactedView.state = viewState
237+
}.store(in: &cancellables)
238+
}
223239
}
224240

225241
// MARK: - Public Interface
@@ -301,9 +317,6 @@ private extension StoreStatsV4PeriodViewController {
301317
// Data
302318
updateStatsDataToDefaultStyles()
303319

304-
// Visibility
305-
updateSiteVisitStats(mode: siteVisitStatsMode)
306-
307320
// Accessibility elements
308321
xAxisAccessibilityView.isAccessibilityElement = true
309322
xAxisAccessibilityView.accessibilityTraits = .staticText
@@ -400,14 +413,6 @@ private extension StoreStatsV4PeriodViewController {
400413
}
401414
}
402415

403-
// MARK: - UI Updates
404-
//
405-
private extension StoreStatsV4PeriodViewController {
406-
func updateSiteVisitStats(mode: SiteVisitStatsMode) {
407-
reloadSiteVisitUI()
408-
}
409-
}
410-
411416
// MARK: - ChartViewDelegate Conformance (Charts)
412417
//
413418
extension StoreStatsV4PeriodViewController: ChartViewDelegate {
@@ -433,36 +438,6 @@ private extension StoreStatsV4PeriodViewController {
433438
func updateUI(selectedBarIndex selectedIndex: Int?) {
434439
viewModel.selectedIntervalIndex = selectedIndex
435440
}
436-
437-
/// Updates visitor and conversion stats based on the selected bar index.
438-
///
439-
/// - Parameter selectedIndex: the index of interval data for the bar chart. Nil if no bar is selected.
440-
func updateSiteVisitStatsAndConversionRate(selectedIndex: Int?) {
441-
let mode: SiteVisitStatsMode
442-
443-
// Hides site visit stats for "today" when an interval bar is selected.
444-
if timeRange == .today, selectedIndex != nil {
445-
mode = .hidden
446-
} else {
447-
mode = siteVisitStatsMode
448-
}
449-
450-
updateSiteVisitStats(mode: mode)
451-
452-
switch siteVisitStatsMode {
453-
case .hidden, .redactedDueToJetpack:
454-
break
455-
case .default:
456-
guard selectedIndex != nil else {
457-
reloadSiteVisitUI()
458-
return
459-
}
460-
guard visitorsDataOrRedactedView != nil else {
461-
return
462-
}
463-
visitorsDataOrRedactedView.state = .data
464-
}
465-
}
466441
}
467442

468443
// MARK: - IAxisValueFormatter Conformance (Charts)
@@ -571,7 +546,6 @@ private extension StoreStatsV4PeriodViewController {
571546

572547
func reloadAllFields(animateChart: Bool = true) {
573548
viewModel.selectedIntervalIndex = nil
574-
reloadSiteVisitUI()
575549
reloadChart(animateChart: animateChart)
576550

577551
view.accessibilityElements = [ordersTitle as Any,
@@ -587,17 +561,6 @@ private extension StoreStatsV4PeriodViewController {
587561
chartAccessibilityView as Any]
588562
}
589563

590-
func reloadSiteVisitUI() {
591-
switch siteVisitStatsMode {
592-
case .hidden:
593-
visitorsDataOrRedactedView.state = .redacted
594-
case .redactedDueToJetpack:
595-
visitorsDataOrRedactedView.state = .redactedDueToJetpack
596-
case .default:
597-
visitorsDataOrRedactedView.state = .data
598-
}
599-
}
600-
601564
func reloadChart(animateChart: Bool = true) {
602565
guard lineChartView != nil else {
603566
return

0 commit comments

Comments
 (0)