Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import UIKit
import WordPressFlux
import Combine

@objc protocol SiteStatsPeriodDelegate {
@objc optional func displayWebViewWithURL(_ url: URL)
Expand All @@ -25,43 +26,24 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController
return ContextManager.sharedInstance().mainContext
}()

var selectedDate: Date?
var selectedPeriod: StatsPeriodUnit? {
didSet {

guard let selectedPeriod else {
return
}

trackPeriodAccessEvent(selectedPeriod)

clearExpandedRows()

// If this is the first time setting the Period, need to initialize the view model.
// Otherwise, just refresh the data.
if oldValue == nil {
initViewModel()
} else {
refreshData()
}
}
}

private let store = StoreContainer.shared.statsPeriod
private var changeReceipt: Receipt?

private var viewModel: SiteStatsPeriodViewModel?
private weak var tableHeaderView: SiteStatsTableHeaderView?
private var viewModel: SiteStatsPeriodViewModel!
private let datePickerViewModel: StatsTrafficDatePickerViewModel
private let datePickerView: StatsTrafficDatePickerView
private var cancellables: Set<AnyCancellable> = []

private let analyticsTracker = BottomScrollAnalyticsTracker()

private lazy var tableHandler: ImmuTableDiffableViewHandler = {
return ImmuTableDiffableViewHandler(takeOver: self, with: analyticsTracker)
}()

init() {
init(date: Date, period: StatsPeriodUnit) {
datePickerViewModel = StatsTrafficDatePickerViewModel(period: period, date: date)
datePickerView = StatsTrafficDatePickerView(viewModel: datePickerViewModel)
super.init(nibName: nil, bundle: nil)

tableStyle = .insetGrouped
}

Expand All @@ -81,31 +63,49 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController
tableView.estimatedRowHeight = 500
tableView.estimatedSectionHeaderHeight = SiteStatsTableHeaderView.estimatedHeight
sendScrollEventsToBanner()

viewModel = SiteStatsPeriodViewModel(store: store,
selectedDate: datePickerViewModel.date,
selectedPeriod: datePickerViewModel.period,
periodDelegate: self,
referrerDelegate: self)
addViewModelListeners()
viewModel.startFetchingOverview()

Publishers.CombineLatest(datePickerViewModel.$date, datePickerViewModel.$period)
.sink(receiveValue: { [weak self] _, _ in
DispatchQueue.main.async {
self?.refreshData()
}
})
.store(in: &cancellables)
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !isMovingToParent {
guard let date = selectedDate, let period = selectedPeriod else {
return
}
addViewModelListeners()
viewModel?.refreshTrafficOverviewData(withDate: date, forPeriod: period)
viewModel.refreshTrafficOverviewData(withDate: datePickerViewModel.date, forPeriod: datePickerViewModel.period)
}
}

// TODO: Replace with a new Date Picker
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard section == 0 else { return nil }
override func initTableView() {
let embeddedDatePickerView = UIView.embedSwiftUIView(datePickerView)
view.addSubview(embeddedDatePickerView)
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)

guard let cell = Bundle.main.loadNibNamed("SiteStatsTableHeaderView", owner: nil, options: nil)?.first as? SiteStatsTableHeaderView else {
return nil
}
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: embeddedDatePickerView.topAnchor, constant: 0),
view.leadingAnchor.constraint(equalTo: embeddedDatePickerView.leadingAnchor, constant: 0),
view.trailingAnchor.constraint(equalTo: embeddedDatePickerView.trailingAnchor, constant: 0),
embeddedDatePickerView.bottomAnchor.constraint(equalTo: tableView.topAnchor, constant: 0),
view.leadingAnchor.constraint(equalTo: tableView.leadingAnchor, constant: 0),
view.trailingAnchor.constraint(equalTo: tableView.trailingAnchor, constant: 0),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0),
])

cell.configure(date: selectedDate, period: selectedPeriod, delegate: self)
cell.animateGhostLayers(viewModel?.isFetchingChart() == true)
tableHeaderView = cell
return cell
tableView.refreshControl = refreshControl
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
Expand All @@ -124,28 +124,12 @@ private extension SiteStatsPeriodTableViewController {

// MARK: - View Model

func initViewModel() {

guard let selectedDate = selectedDate,
let selectedPeriod = selectedPeriod else {
return
}

viewModel = SiteStatsPeriodViewModel(store: store,
selectedDate: selectedDate,
selectedPeriod: selectedPeriod,
periodDelegate: self,
referrerDelegate: self)
addViewModelListeners()
viewModel?.startFetchingOverview()
}

func addViewModelListeners() {
if changeReceipt != nil {
return
}

changeReceipt = viewModel?.onChange { [weak self] in
changeReceipt = viewModel.onChange { [weak self] in
self?.refreshTableView()
}
}
Expand All @@ -172,14 +156,9 @@ private extension SiteStatsPeriodTableViewController {
// MARK: - Table Refreshing

func refreshTableView() {
guard let viewModel = viewModel else {
return
}

tableHandler.diffableDataSource.apply(viewModel.tableViewSnapshot(), animatingDifferences: false)

refreshControl.endRefreshing()
tableHeaderView?.animateGhostLayers(viewModel.isFetchingChart() == true)

if viewModel.fetchingFailed() {
displayFailureViewIfNecessary()
Expand All @@ -193,14 +172,12 @@ private extension SiteStatsPeriodTableViewController {
}

func refreshData() {
guard let selectedDate = selectedDate,
let selectedPeriod = selectedPeriod,
viewIsVisible() else {
guard viewIsVisible() else {
refreshControl.endRefreshing()
return
return
}
addViewModelListeners()
viewModel?.refreshTrafficOverviewData(withDate: selectedDate, forPeriod: selectedPeriod)
viewModel.refreshTrafficOverviewData(withDate: datePickerViewModel.date, forPeriod: datePickerViewModel.period)
}

func applyTableUpdates() {
Expand Down Expand Up @@ -303,8 +280,8 @@ extension SiteStatsPeriodTableViewController: SiteStatsPeriodDelegate {

let detailTableViewController = SiteStatsDetailTableViewController.loadFromStoryboard()
detailTableViewController.configure(statSection: statSection,
selectedDate: selectedDate,
selectedPeriod: selectedPeriod)
selectedDate: datePickerViewModel.date,
selectedPeriod: datePickerViewModel.period)
navigationController?.pushViewController(detailTableViewController, animated: true)
}

Expand All @@ -318,8 +295,8 @@ extension SiteStatsPeriodTableViewController: SiteStatsPeriodDelegate {
}

func barChartTabSelected(_ tabIndex: StatsTrafficBarChartTabIndex) {
if let tab = StatsTrafficBarChartTabs(rawValue: tabIndex), let period = selectedPeriod {
trackBarChartTabSelectionEvent(tab: tab, period: period)
if let tab = StatsTrafficBarChartTabs(rawValue: tabIndex) {
trackBarChartTabSelectionEvent(tab: tab, period: datePickerViewModel.period)
}
}
}
Expand All @@ -332,21 +309,6 @@ extension SiteStatsPeriodTableViewController: SiteStatsReferrerDelegate {
}
}

// MARK: - SiteStatsTableHeaderDelegate Methods

extension SiteStatsPeriodTableViewController: SiteStatsTableHeaderDateButtonDelegate {
func dateChangedTo(_ newDate: Date?) {
selectedDate = newDate
refreshData()
}

func didTouchHeaderButton(forward: Bool) {
if let intervalDate = viewModel?.updateDate(forward: forward) {
tableHeaderView?.updateDate(with: intervalDate)
}
}
}

// MARK: Jetpack powered banner

private extension SiteStatsPeriodTableViewController {
Expand All @@ -361,23 +323,6 @@ private extension SiteStatsPeriodTableViewController {
// MARK: - Tracking

private extension SiteStatsPeriodTableViewController {
func trackPeriodAccessEvent(_ period: StatsPeriodUnit) {
let event: WPAnalyticsStat = {
switch period {
case .day:
return .statsPeriodDaysAccessed
case .week:
return .statsPeriodWeeksAccessed
case .month:
return .statsPeriodMonthsAccessed
case .year:
return .statsPeriodYearsAccessed
}
}()

WPAppAnalytics.track(event)
}

func trackBarChartTabSelectionEvent(tab: StatsTrafficBarChartTabs, period: StatsPeriodUnit) {
let properties: [AnyHashable: Any] = [StatsPeriodUnit.analyticsPeriodKey: period.description as Any]
WPAppAnalytics.track(tab.analyticsEvent, withProperties: properties)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,18 @@ class SiteStatsDashboardViewController: UIViewController {

private var insightsTableViewController = SiteStatsInsightsTableViewController.loadFromStoryboard()
private lazy var periodTableViewControllerDeprecated = SiteStatsPeriodTableViewControllerDeprecated.loadFromStoryboard()
private lazy var trafficTableViewController = SiteStatsPeriodTableViewController()
private lazy var trafficTableViewController = {
let date: Date
if let selectedDate = getLastSelectedDateFromUserDefaults() {
date = selectedDate
} else {
date = StatsDataHelper.currentDateForSite()
}

let currentPeriod = StatsPeriodUnit(rawValue: currentSelectedPeriod.rawValue - 1) ?? .day

return SiteStatsPeriodTableViewController(date: date, period: currentPeriod)
}()
private var pageViewController: UIPageViewController?
private lazy var displayedPeriods: [StatsPeriodType] = StatsPeriodType.displayedPeriods

Expand Down Expand Up @@ -250,7 +261,6 @@ private extension SiteStatsDashboardViewController {

func restoreSelectedDateFromUserDefaults() {
periodTableViewControllerDeprecated.selectedDate = getLastSelectedDateFromUserDefaults()
trafficTableViewController.selectedDate = getLastSelectedDateFromUserDefaults()
removeLastSelectedDateFromUserDefaults()
}

Expand Down Expand Up @@ -278,13 +288,6 @@ private extension SiteStatsDashboardViewController {
direction: .forward,
animated: false)
}

if trafficTableViewController.selectedDate == nil {
trafficTableViewController.selectedDate = StatsDataHelper.currentDateForSite()
}

let selectedPeriod = StatsPeriodUnit(rawValue: currentSelectedPeriod.rawValue - 1) ?? .day
trafficTableViewController.selectedPeriod = selectedPeriod
case .days, .weeks, .months, .years:
if previousSelectedPeriodWasInsights || pageViewControllerIsEmpty {
pageViewController?.setViewControllers([periodTableViewControllerDeprecated],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import SwiftUI
import DesignSystem

struct StatsTrafficDatePickerView: View {
@ObservedObject var viewModel: StatsTrafficDatePickerViewModel

var body: some View {
HStack {
Menu {
ForEach([StatsPeriodUnit.day, .week, .month, .year], id: \.self) { period in
Button(period.label, action: {
viewModel.period = period
})
}
} label: {
Text(viewModel.period.label)
.style(TextStyle.bodySmall(.emphasized))
.foregroundColor(Color.DS.Foreground.primary)
Image(systemName: "chevron.down")
.font(.system(size: 8))
.foregroundColor(Color.DS.Foreground.secondary)

}
.menuStyle(.borderlessButton)
.padding(.vertical, Length.Padding.single)
.padding(.horizontal, Length.Padding.double)
.background(Color.DS.Background.secondary)
.clipShape(RoundedRectangle(cornerRadius: Length.Radius.max))
.overlay(
RoundedRectangle(cornerRadius: Length.Radius.max)
.strokeBorder(.clear, lineWidth: 0)
)

Spacer()

Text(viewModel.formattedCurrentPeriod())
.style(TextStyle.bodySmall(.emphasized))
.foregroundColor(Color.DS.Foreground.primary)
.lineLimit(1)

Spacer().frame(width: Length.Padding.split)

Button(action: {
viewModel.goToPreviousPeriod()
}) {
Image(systemName: "chevron.left")
.imageScale(.small)
.foregroundColor(Color.DS.Foreground.secondary)
.flipsForRightToLeftLayoutDirection(true)
}
.padding(.trailing, Length.Padding.single)

let isNextDisabled = !viewModel.isNextPeriodAvailable
let enabledColor = Color.DS.Foreground.secondary
let disabledColor = enabledColor.opacity(0.5)

Button(action: {
viewModel.goToNextPeriod()
}) {
Image(systemName: "chevron.right")
.imageScale(.small)
.foregroundColor(isNextDisabled ? disabledColor : enabledColor)
.flipsForRightToLeftLayoutDirection(true)
}
.disabled(isNextDisabled)
}.padding(.vertical, Length.Padding.single)
.padding(.horizontal, Length.Padding.double)
.background(Color.DS.Background.primary)
.overlay(
Rectangle()
.frame(height: Length.Border.thin)
.foregroundColor(Color.DS.Foreground.tertiary),
alignment: .bottom
)
.background(Color.DS.Background.secondary)
}
}

private extension StatsPeriodUnit {
var label: String {
switch self {
case .day:
return NSLocalizedString("stats.traffic.day", value: "By day", comment: "The label for the option to show Stats Traffic by day.")
case .week:
return NSLocalizedString("stats.traffic.week", value: "By week", comment: "The label for the option to show Stats Traffic by week.")
case .month:
return NSLocalizedString("stats.traffic.month", value: "By month", comment: "The label for the option to show Stats Traffic by month.")
case .year:
return NSLocalizedString("stats.traffic.year", value: "By year", comment: "The label for the option to show Stats Traffic by year.")
}
}
}
Loading