Skip to content

Commit 779d3a7

Browse files
authored
Merge pull request #5451 from woocommerce/issue/5361-JCP-empty-store-visit-stats
Jetpack CP: Add empty view for site visit stats
2 parents afa8e10 + 8967f32 commit 779d3a7

File tree

6 files changed

+145
-46
lines changed

6 files changed

+145
-46
lines changed

WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ private extension DashboardViewController {
414414
ServiceLocator.stores.site.sink { [weak self] site in
415415
guard let self = self else { return }
416416
self.updateUI(site: site)
417+
self.reloadData(forced: true)
417418
}.store(in: &cancellables)
418419
}
419420
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ final class StoreStatsAndTopPerformersPeriodViewController: UIViewController {
2222
let granularity: StatsGranularityV4
2323

2424
/// Whether site visit stats can be shown
25-
var shouldShowSiteVisitStats: Bool = true {
25+
var siteVisitStatsMode: SiteVisitStatsMode = .default {
2626
didSet {
27-
storeStatsPeriodViewController.updateSiteVisitStatsVisibility(shouldShowSiteVisitStats: shouldShowSiteVisitStats)
27+
storeStatsPeriodViewController.siteVisitStatsMode = siteVisitStatsMode
2828
}
2929
}
3030

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ private extension StoreStatsAndTopPerformersViewController {
124124
onCompletion?(.failure(error))
125125
} else {
126126
self?.lastFullSyncTimestamp = Date()
127-
self?.showSiteVisitors(true)
127+
self?.updateSiteVisitors(mode: .default)
128128
onCompletion?(.success(()))
129129
}
130130
}
@@ -401,16 +401,24 @@ private extension StoreStatsAndTopPerformersViewController {
401401
}
402402

403403
private extension StoreStatsAndTopPerformersViewController {
404-
func showSiteVisitors(_ shouldShowSiteVisitors: Bool) {
404+
func updateSiteVisitors(mode: SiteVisitStatsMode) {
405405
periodVCs.forEach { vc in
406-
vc.shouldShowSiteVisitStats = shouldShowSiteVisitors
406+
vc.siteVisitStatsMode = mode
407407
}
408408
}
409409

410410
func handleSiteVisitStatsStoreError(error: SiteVisitStatsStoreError) {
411411
switch error {
412-
case .statsModuleDisabled, .noPermission:
413-
showSiteVisitors(false)
412+
case .noPermission:
413+
updateSiteVisitors(mode: .hidden)
414+
case .statsModuleDisabled:
415+
let defaultSite = ServiceLocator.stores.sessionManager.defaultSite
416+
let jcpFeatureFlagEnabled = ServiceLocator.featureFlagService.isFeatureFlagEnabled(.jetpackConnectionPackageSupport)
417+
if defaultSite?.isJetpackCPConnected == true, jcpFeatureFlagEnabled {
418+
updateSiteVisitors(mode: .redactedDueToJetpack)
419+
} else {
420+
updateSiteVisitors(mode: .hidden)
421+
}
414422
default:
415423
displaySyncingError()
416424
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import UIKit
2+
3+
final class StoreStatsSiteVisitEmptyView: UIView {
4+
convenience init() {
5+
self.init(frame: .zero)
6+
}
7+
8+
override init(frame: CGRect) {
9+
super.init(frame: frame)
10+
setupSubviews()
11+
}
12+
13+
required init?(coder: NSCoder) {
14+
fatalError("init(coder:) has not been implemented")
15+
}
16+
17+
private func setupSubviews() {
18+
translatesAutoresizingMaskIntoConstraints = false
19+
20+
let emptyView = UIView(frame: .zero)
21+
emptyView.backgroundColor = .systemColor(.systemGroupedBackground)
22+
emptyView.layer.cornerRadius = 2.0
23+
emptyView.translatesAutoresizingMaskIntoConstraints = false
24+
25+
let jetpackImageView = UIImageView(image: .jetpackLogoImage.withRenderingMode(.alwaysTemplate))
26+
jetpackImageView.contentMode = .scaleAspectFit
27+
jetpackImageView.tintColor = .jetpackGreen
28+
jetpackImageView.translatesAutoresizingMaskIntoConstraints = false
29+
30+
addSubview(emptyView)
31+
addSubview(jetpackImageView)
32+
33+
NSLayoutConstraint.activate([
34+
self.widthAnchor.constraint(equalToConstant: 48),
35+
emptyView.widthAnchor.constraint(equalToConstant: 32),
36+
emptyView.heightAnchor.constraint(equalToConstant: 10),
37+
emptyView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
38+
emptyView.topAnchor.constraint(equalTo: jetpackImageView.bottomAnchor),
39+
jetpackImageView.widthAnchor.constraint(equalToConstant: 14),
40+
jetpackImageView.heightAnchor.constraint(equalToConstant: 14),
41+
jetpackImageView.leadingAnchor.constraint(equalTo: emptyView.trailingAnchor),
42+
jetpackImageView.topAnchor.constraint(equalTo: self.topAnchor, constant: 4)
43+
])
44+
}
45+
}

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

Lines changed: 80 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,25 @@ import Charts
22
import UIKit
33
import Yosemite
44

5+
/// Different display modes of site visit stats
6+
///
7+
enum SiteVisitStatsMode {
8+
case `default`
9+
case redactedDueToJetpack
10+
case hidden
11+
}
12+
513
/// Shows the store stats with v4 API for a time range.
614
///
7-
class StoreStatsV4PeriodViewController: UIViewController {
15+
final class StoreStatsV4PeriodViewController: UIViewController {
816

917
// MARK: - Public Properties
1018

1119
let granularity: StatsGranularityV4
1220

13-
var shouldShowSiteVisitStats: Bool = true {
21+
var siteVisitStatsMode: SiteVisitStatsMode = .default {
1422
didSet {
15-
updateSiteVisitStatsVisibility(shouldShowSiteVisitStats: shouldShowSiteVisitStats)
23+
updateSiteVisitStats(mode: siteVisitStatsMode)
1624
}
1725
}
1826

@@ -145,6 +153,7 @@ class StoreStatsV4PeriodViewController: UIViewController {
145153
roundSmallNumbers: false) ?? String()
146154
}
147155

156+
private lazy var visitorsEmptyView = StoreStatsSiteVisitEmptyView()
148157
// MARK: - Initialization
149158

150159
/// Designated Initializer
@@ -270,6 +279,12 @@ private extension StoreStatsV4PeriodViewController {
270279
timeRangeBarView.backgroundColor = .systemColor(.secondarySystemGroupedBackground)
271280
visitorsStackView.backgroundColor = .systemColor(.secondarySystemGroupedBackground)
272281

282+
// Visitor empty view - insert it at the second-to-last index,
283+
// since we need the footer view (with height = 20) as the last item in the stack view.
284+
let emptyViewIndex = max(0, visitorsStackView.arrangedSubviews.count - 2)
285+
visitorsStackView.insertArrangedSubview(visitorsEmptyView, at: emptyViewIndex)
286+
visitorsEmptyView.isHidden = true
287+
273288
// Time range bar bottom border view
274289
timeRangeBarBottomBorderView.backgroundColor = .systemColor(.separator)
275290

@@ -292,7 +307,7 @@ private extension StoreStatsV4PeriodViewController {
292307
lastUpdated.backgroundColor = .listForeground
293308

294309
// Visibility
295-
updateSiteVisitStatsVisibility(shouldShowSiteVisitStats: shouldShowSiteVisitStats)
310+
updateSiteVisitStats(mode: siteVisitStatsMode)
296311

297312
// Accessibility elements
298313
xAxisAccessibilityView.isAccessibilityElement = true
@@ -402,9 +417,16 @@ private extension StoreStatsV4PeriodViewController {
402417

403418
// MARK: - UI Updates
404419
//
405-
extension StoreStatsV4PeriodViewController {
406-
func updateSiteVisitStatsVisibility(shouldShowSiteVisitStats: Bool) {
407-
visitorsStackView?.isHidden = !shouldShowSiteVisitStats
420+
private extension StoreStatsV4PeriodViewController {
421+
func updateSiteVisitStats(mode: SiteVisitStatsMode) {
422+
// Hides site visit stats for "today"
423+
guard timeRange != .today else {
424+
visitorsStackView.isHidden = true
425+
return
426+
}
427+
428+
visitorsStackView.isHidden = mode == .hidden
429+
reloadSiteFields()
408430
}
409431
}
410432

@@ -463,30 +485,28 @@ private extension StoreStatsV4PeriodViewController {
463485
///
464486
/// - Parameter selectedIndex: the index of interval data for the bar chart. Nil if no bar is selected.
465487
func updateSiteVisitStats(selectedIndex: Int?) {
466-
guard shouldShowSiteVisitStats else {
467-
return
468-
}
469-
guard let selectedIndex = selectedIndex else {
470-
updateSiteVisitStatsVisibility(shouldShowSiteVisitStats: shouldShowSiteVisitStats)
471-
reloadSiteFields()
472-
return
473-
}
474-
// Hides site visit stats for "today".
475-
guard timeRange != .today else {
476-
updateSiteVisitStatsVisibility(shouldShowSiteVisitStats: false)
477-
return
478-
}
479-
updateSiteVisitStatsVisibility(shouldShowSiteVisitStats: true)
480-
guard visitorsData != nil else {
481-
return
482-
}
483-
484-
var visitorsText = Constants.placeholderText
485-
if selectedIndex < siteStatsItems.count {
486-
let siteStatsItem = siteStatsItems[selectedIndex]
487-
visitorsText = Double(siteStatsItem.visitors).humanReadableString()
488+
updateSiteVisitStats(mode: siteVisitStatsMode)
489+
490+
switch siteVisitStatsMode {
491+
case .hidden, .redactedDueToJetpack:
492+
break
493+
case .default:
494+
guard let selectedIndex = selectedIndex else {
495+
reloadSiteFields()
496+
return
497+
}
498+
guard visitorsData != nil else {
499+
return
500+
}
501+
var visitorsText = Constants.placeholderText
502+
if selectedIndex < siteStatsItems.count {
503+
let siteStatsItem = siteStatsItems[selectedIndex]
504+
visitorsText = Double(siteStatsItem.visitors).humanReadableString()
505+
}
506+
visitorsData.text = visitorsText
507+
visitorsData.isHidden = false
508+
visitorsEmptyView.isHidden = true
488509
}
489-
visitorsData.text = visitorsText
490510
}
491511

492512
/// Updates date bar based on the selected bar index.
@@ -655,8 +675,19 @@ private extension StoreStatsV4PeriodViewController {
655675
reloadSiteFields()
656676
reloadChart(animateChart: animateChart)
657677
reloadLastUpdatedField()
658-
let visitStatsElements = shouldShowSiteVisitStats ? [visitorsTitle as Any,
659-
visitorsData as Any]: []
678+
let visitStatsElements: [Any] = {
679+
switch siteVisitStatsMode {
680+
case .default:
681+
return [visitorsTitle as Any,
682+
visitorsData as Any]
683+
case .redactedDueToJetpack:
684+
return [visitorsTitle as Any,
685+
visitorsEmptyView as Any]
686+
case .hidden:
687+
return []
688+
}
689+
}()
690+
660691
view.accessibilityElements = visitStatsElements + [ordersTitle as Any,
661692
ordersData as Any,
662693
revenueTitle as Any,
@@ -685,15 +716,25 @@ private extension StoreStatsV4PeriodViewController {
685716
}
686717

687718
func reloadSiteFields() {
688-
guard visitorsData != nil else {
689-
return
690-
}
719+
switch siteVisitStatsMode {
720+
case .hidden:
721+
break
722+
case .redactedDueToJetpack:
723+
visitorsData.isHidden = true
724+
visitorsEmptyView.isHidden = false
725+
case .default:
726+
guard visitorsData != nil else {
727+
return
728+
}
691729

692-
var visitorsText = Constants.placeholderText
693-
if let siteStats = siteStats {
694-
visitorsText = Double(siteStats.totalVisitors).humanReadableString()
730+
var visitorsText = Constants.placeholderText
731+
if let siteStats = siteStats {
732+
visitorsText = Double(siteStats.totalVisitors).humanReadableString()
733+
}
734+
visitorsData.text = visitorsText
735+
visitorsData.isHidden = false
736+
visitorsEmptyView.isHidden = true
695737
}
696-
visitorsData.text = visitorsText
697738
}
698739

699740
func reloadChart(animateChart: Bool = true) {

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,7 @@
13681368
DEC2962526C122DF005A056B /* ShippingLabelCustomsFormInputViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC2962426C122DF005A056B /* ShippingLabelCustomsFormInputViewModel.swift */; };
13691369
DEC2962726C17AD8005A056B /* ShippingLabelCustomsForm+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC2962626C17AD8005A056B /* ShippingLabelCustomsForm+Localization.swift */; };
13701370
DEC2962926C20ECB005A056B /* CollapsibleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC2962826C20ECB005A056B /* CollapsibleView.swift */; };
1371+
DEC6C51827466B59006832D3 /* StoreStatsSiteVisitEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC6C51727466B59006832D3 /* StoreStatsSiteVisitEmptyView.swift */; };
13711372
DEDB886B26E8531E00981595 /* ShippingLabelPackageAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEDB886A26E8531E00981595 /* ShippingLabelPackageAttributes.swift */; };
13721373
DEE6437626D87C4100888A75 /* PrintCustomsFormsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEE6437526D87C4100888A75 /* PrintCustomsFormsView.swift */; };
13731374
DEE6437826D8DAD900888A75 /* InProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEE6437726D8DAD900888A75 /* InProgressView.swift */; };
@@ -2854,6 +2855,7 @@
28542855
DEC2962426C122DF005A056B /* ShippingLabelCustomsFormInputViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelCustomsFormInputViewModel.swift; sourceTree = "<group>"; };
28552856
DEC2962626C17AD8005A056B /* ShippingLabelCustomsForm+Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShippingLabelCustomsForm+Localization.swift"; sourceTree = "<group>"; };
28562857
DEC2962826C20ECB005A056B /* CollapsibleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleView.swift; sourceTree = "<group>"; };
2858+
DEC6C51727466B59006832D3 /* StoreStatsSiteVisitEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreStatsSiteVisitEmptyView.swift; sourceTree = "<group>"; };
28572859
DEDB886A26E8531E00981595 /* ShippingLabelPackageAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelPackageAttributes.swift; sourceTree = "<group>"; };
28582860
DEE6437526D87C4100888A75 /* PrintCustomsFormsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrintCustomsFormsView.swift; sourceTree = "<group>"; };
28592861
DEE6437726D8DAD900888A75 /* InProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InProgressView.swift; sourceTree = "<group>"; };
@@ -3646,6 +3648,7 @@
36463648
02E4FD7D2306A8180049610C /* StatsTimeRangeBarViewModel.swift */,
36473649
0240B3AB230A910C000A866C /* StoreStatsV4ChartAxisHelper.swift */,
36483650
5778E00524DB0C3900B65CBF /* StoreStatsAndTopPerformersPeriodViewModel.swift */,
3651+
DEC6C51727466B59006832D3 /* StoreStatsSiteVisitEmptyView.swift */,
36493652
);
36503653
path = "Stats v4";
36513654
sourceTree = "<group>";
@@ -7539,6 +7542,7 @@
75397542
0260F40123224E8100EDA10A /* ProductsViewController.swift in Sources */,
75407543
7E6A019F2725CD76001668D5 /* FilterProductCategoryListViewModel.swift in Sources */,
75417544
4569D3C325DC008700CDC3E2 /* SiteAddress.swift in Sources */,
7545+
DEC6C51827466B59006832D3 /* StoreStatsSiteVisitEmptyView.swift in Sources */,
75427546
31FE28C225E6D338003519F2 /* LearnMoreTableViewCell.swift in Sources */,
75437547
02D45647231CB1FB008CF0A9 /* UIImage+Dot.swift in Sources */,
75447548
E11228BE2707267F004E9F2D /* CardPresentModalUpdateFailedNonRetryable.swift in Sources */,

0 commit comments

Comments
 (0)