diff --git a/Networking/Networking/Model/Stats/TopEarnerStatsItem.swift b/Networking/Networking/Model/Stats/TopEarnerStatsItem.swift
index 17a34380c45..63a33076795 100644
--- a/Networking/Networking/Model/Stats/TopEarnerStatsItem.swift
+++ b/Networking/Networking/Model/Stats/TopEarnerStatsItem.swift
@@ -77,7 +77,12 @@ extension TopEarnerStatsItem: Comparable {
}
public static func < (lhs: TopEarnerStatsItem, rhs: TopEarnerStatsItem) -> Bool {
- return lhs.quantity < rhs.quantity ||
- (lhs.quantity == rhs.quantity && lhs.productName < rhs.productName)
+ return lhs.total < rhs.total ||
+ (lhs.total == rhs.total && lhs.quantity < rhs.quantity)
+ }
+
+ public static func > (lhs: TopEarnerStatsItem, rhs: TopEarnerStatsItem) -> Bool {
+ return lhs.total > rhs.total ||
+ (lhs.total == rhs.total && lhs.quantity > rhs.quantity)
}
}
diff --git a/WooCommerce/Classes/AppDelegate.swift b/WooCommerce/Classes/AppDelegate.swift
index 2cdea92b080..167b178f279 100644
--- a/WooCommerce/Classes/AppDelegate.swift
+++ b/WooCommerce/Classes/AppDelegate.swift
@@ -135,6 +135,7 @@ private extension AppDelegate {
UINavigationBar.appearance().isTranslucent = false
UINavigationBar.appearance().tintColor = .white
UIApplication.shared.statusBarStyle = .lightContent
+ UILabel.appearance(whenContainedInInstancesOf: [UITableViewHeaderFooterView.self]).textColor = StyleManager.sectionTitleColor
// Take advantage of a bug in UIAlertController
// to style all UIAlertControllers with WC color
diff --git a/WooCommerce/Classes/Extensions/Array+Helpers.swift b/WooCommerce/Classes/Extensions/Array+Helpers.swift
index a83e3fc58b5..fa9edcb9168 100644
--- a/WooCommerce/Classes/Extensions/Array+Helpers.swift
+++ b/WooCommerce/Classes/Extensions/Array+Helpers.swift
@@ -15,3 +15,12 @@ extension Array {
return removeFirst()
}
}
+
+extension Collection {
+
+ /// Returns the element at the specified index if it is within bounds, otherwise nil.
+ ///
+ subscript (safe index: Index) -> Element? {
+ return indices.contains(index) ? self[index] : nil
+ }
+}
diff --git a/WooCommerce/Classes/Extensions/UIImage+Woo.swift b/WooCommerce/Classes/Extensions/UIImage+Woo.swift
index a14fd3f2989..1439ced5673 100644
--- a/WooCommerce/Classes/Extensions/UIImage+Woo.swift
+++ b/WooCommerce/Classes/Extensions/UIImage+Woo.swift
@@ -21,6 +21,13 @@ extension UIImage {
return Gridicon.iconOfType(.chevronRight).imageWithTintColor(tintColor)!
}
+ /// Product Placeholder Image
+ ///
+ static var productPlaceholderImage: UIImage {
+ let tintColor = StyleManager.wooGreyLight
+ return Gridicon.iconOfType(.product).imageWithTintColor(tintColor)!
+ }
+
/// Gravatar Placeholder Image
///
static var gravatarPlaceholderImage: UIImage {
diff --git a/WooCommerce/Classes/Extensions/UILabel+Helpers.swift b/WooCommerce/Classes/Extensions/UILabel+Helpers.swift
index 3582aac9acc..ed1b67ed94e 100644
--- a/WooCommerce/Classes/Extensions/UILabel+Helpers.swift
+++ b/WooCommerce/Classes/Extensions/UILabel+Helpers.swift
@@ -9,6 +9,12 @@ extension UILabel {
textColor = StyleManager.defaultTextColor
}
+ func applySubheadlineStyle() {
+ adjustsFontForContentSizeCategory = true
+ font = .subheadline
+ textColor = StyleManager.defaultTextColor
+ }
+
func applyBodyStyle() {
adjustsFontForContentSizeCategory = true
font = .body
diff --git a/WooCommerce/Classes/Model/TopEarnerStatsItem+Woo.swift b/WooCommerce/Classes/Model/TopEarnerStatsItem+Woo.swift
new file mode 100644
index 00000000000..6051749abfb
--- /dev/null
+++ b/WooCommerce/Classes/Model/TopEarnerStatsItem+Woo.swift
@@ -0,0 +1,27 @@
+import Foundation
+import Yosemite
+
+
+// MARK: - TopEarnerStatsItem Helper Methods
+//
+extension TopEarnerStatsItem {
+
+ /// Returns the Currency Symbol associated with the current TopEarnerStatsItem.
+ ///
+ var currencySymbol: String {
+ guard !currency.isEmpty else {
+ return ""
+ }
+ guard let identifier = Locale.availableIdentifiers.first(where: { Locale(identifier: $0).currencyCode == currency }) else {
+ return currency
+ }
+
+ return Locale(identifier: identifier).currencySymbol ?? currency
+ }
+
+ /// Returns a friendly-formatted total string including the currency symbol
+ ///
+ var formattedTotalString: String {
+ return currencySymbol + total.friendlyString()
+ }
+}
diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Dashboard.storyboard b/WooCommerce/Classes/ViewRelated/Dashboard/Dashboard.storyboard
index d83042c0123..ff0d34ab011 100644
--- a/WooCommerce/Classes/ViewRelated/Dashboard/Dashboard.storyboard
+++ b/WooCommerce/Classes/ViewRelated/Dashboard/Dashboard.storyboard
@@ -90,14 +90,14 @@
-
+
-
+
-
+
@@ -115,21 +115,25 @@
-
+
-
-
+
+
-
-
+
+
-
+
+
-
+
+
+
+
@@ -154,6 +158,7 @@
+
@@ -215,18 +220,18 @@
-
+
-
+
-
+
@@ -268,6 +273,7 @@
+
@@ -289,6 +295,13 @@
+
+
+
+
+
+
+
-
-
-
-
+
+
+
-
+
@@ -325,7 +337,89 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift
index 8d4425e3060..cdc196f1cfb 100644
--- a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift
+++ b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift
@@ -15,6 +15,7 @@ class DashboardViewController: UIViewController {
private var storeStatsViewController: StoreStatsViewController!
private var newOrdersViewController: NewOrdersViewController!
+ private var topPerformersViewController: TopPerformersViewController!
private lazy var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
@@ -37,13 +38,16 @@ class DashboardViewController: UIViewController {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
- if let vc = segue.destination as? StoreStatsViewController, segue.identifier == Constants.storeStatsSegue {
+ if let vc = segue.destination as? StoreStatsViewController, segue.identifier == Segues.storeStatsSegue {
storeStatsViewController = vc
}
- if let vc = segue.destination as? NewOrdersViewController, segue.identifier == Constants.newOrdersSegue {
+ if let vc = segue.destination as? NewOrdersViewController, segue.identifier == Segues.newOrdersSegue {
newOrdersViewController = vc
newOrdersViewController.delegate = self
}
+ if let vc = segue.destination as? TopPerformersViewController, segue.identifier == Segues.topPerformersSegue {
+ topPerformersViewController = vc
+ }
}
}
@@ -86,7 +90,7 @@ private extension DashboardViewController {
private extension DashboardViewController {
@objc func settingsTapped() {
- performSegue(withIdentifier: Constants.settingsSegue, sender: nil)
+ performSegue(withIdentifier: Segues.settingsSegue, sender: nil)
}
@objc func pullToRefresh() {
@@ -116,6 +120,7 @@ private extension DashboardViewController {
DDLogInfo("♻️ Requesting dashboard data be reloaded...")
storeStatsViewController.syncAllStats()
newOrdersViewController.syncNewOrders()
+ topPerformersViewController.syncTopPerformers()
refreshControl.endRefreshing()
}
@@ -149,14 +154,18 @@ private extension DashboardViewController {
// MARK: - Constants
//
private extension DashboardViewController {
+
+ struct Segues {
+ static let settingsSegue = "ShowSettingsViewController"
+ static let storeStatsSegue = "StoreStatsEmbedSegue"
+ static let newOrdersSegue = "NewOrdersEmbedSegue"
+ static let topPerformersSegue = "TopPerformersEmbedSegue"
+ }
+
struct Constants {
- static let settingsSegue = "ShowSettingsViewController"
- static let storeStatsSegue = "StoreStatsEmbedSegue"
- static let newOrdersSegue = "NewOrdersEmbedSegue"
-
- static let hideAnimationDuration: TimeInterval = 0.25
- static let showAnimationDuration: TimeInterval = 0.50
- static let showSpringDamping: CGFloat = 0.7
- static let showSpringVelocity: CGFloat = 0.0
+ static let hideAnimationDuration: TimeInterval = 0.25
+ static let showAnimationDuration: TimeInterval = 0.50
+ static let showSpringDamping: CGFloat = 0.7
+ static let showSpringVelocity: CGFloat = 0.0
}
}
diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/NoPeriodDataTableViewCell.swift b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/NoPeriodDataTableViewCell.swift
new file mode 100644
index 00000000000..21044601963
--- /dev/null
+++ b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/NoPeriodDataTableViewCell.swift
@@ -0,0 +1,18 @@
+import Foundation
+import UIKit
+
+
+/// Displayed whenever there is no top performer data for a given period (granularity)
+///
+class NoPeriodDataTableViewCell: UITableViewCell {
+ @IBOutlet private weak var mainImageView: UIImageView!
+
+ /// LegendLabel: To be displayed below the ImageView.
+ ///
+ @IBOutlet private var legendLabel: UILabel! {
+ didSet {
+ legendLabel.applySubheadlineStyle()
+ legendLabel.text = NSLocalizedString("No activity this period", comment: "Default text for Top Performers section when no data exists for a given period.")
+ }
+ }
+}
diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/NoPeriodDataTableViewCell.xib b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/NoPeriodDataTableViewCell.xib
new file mode 100644
index 00000000000..edf24d69d03
--- /dev/null
+++ b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/NoPeriodDataTableViewCell.xib
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/ProductTableViewCell.swift b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/ProductTableViewCell.swift
new file mode 100644
index 00000000000..aa84a5dd7d2
--- /dev/null
+++ b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/ProductTableViewCell.swift
@@ -0,0 +1,65 @@
+import UIKit
+import Yosemite
+import WordPressUI
+import Gridicons
+
+class ProductTableViewCell: UITableViewCell {
+
+ // MARK: - Properties
+
+ @IBOutlet private weak var productImage: UIImageView!
+ @IBOutlet private var nameLabel: UILabel!
+ @IBOutlet private var detailLabel: UILabel!
+ @IBOutlet private var priceLabel: UILabel!
+
+ var nameText: String? {
+ get {
+ return nameLabel.text
+ }
+ set {
+ nameLabel.text = newValue
+ }
+ }
+
+ var detailText: String? {
+ get {
+ return detailLabel.text
+ }
+ set {
+ detailLabel.text = newValue
+ }
+ }
+
+ var priceText: String? {
+ get {
+ return priceLabel.text
+ }
+ set {
+ priceLabel.text = newValue
+ }
+ }
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ nameLabel.applyBodyStyle()
+ priceLabel.applyBodyStyle()
+ detailLabel.applyFootnoteStyle()
+ productImage.contentMode = .scaleAspectFit
+ }
+}
+
+// MARK: - Public Methods
+//
+extension ProductTableViewCell {
+ func configure(_ statsItem: TopEarnerStatsItem?) {
+ nameText = statsItem?.productName
+ detailText = String.localizedStringWithFormat( NSLocalizedString("Total Product Order: %ld", comment: "Top performers — label for the total number of products ordered"), statsItem?.quantity ?? 0)
+ priceText = statsItem?.formattedTotalString
+
+ if let productURLString = statsItem?.imageUrl {
+ productImage.downloadImage(from: URL(string: productURLString), placeholderImage: UIImage.productPlaceholderImage)
+ } else {
+ productImage.image = .productPlaceholderImage
+ }
+ }
+}
diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/ProductTableViewCell.xib b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/ProductTableViewCell.xib
new file mode 100644
index 00000000000..9124f117f0f
--- /dev/null
+++ b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/ProductTableViewCell.xib
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/TopPerformersHeaderView.swift b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/TopPerformersHeaderView.swift
new file mode 100644
index 00000000000..c03a67330ba
--- /dev/null
+++ b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/TopPerformersHeaderView.swift
@@ -0,0 +1,79 @@
+import UIKit
+
+
+/// Renders a section header for top performers with a main description label and two column labels [Left / Right].
+///
+class TopPerformersHeaderView: UITableViewHeaderFooterView {
+
+ /// Main Description Label
+ ///
+ @IBOutlet private weak var descriptionLabel: UILabel!
+
+ /// Left Label
+ ///
+ @IBOutlet private weak var leftColumn: UILabel!
+
+ /// Right Label
+ ///
+ @IBOutlet private weak var rightColumn: UILabel!
+
+ /// Bottom Border View
+ ///
+ @IBOutlet private weak var borderView: UIView!
+
+ /// Description Label's Text
+ ///
+ var descriptionText: String? {
+ get {
+ return descriptionLabel.text
+ }
+ set {
+ descriptionLabel.text = newValue
+ }
+ }
+
+ /// Left Label's Text
+ ///
+ var leftText: String? {
+ get {
+ return leftColumn.text
+ }
+ set {
+ leftColumn.text = newValue
+ }
+ }
+
+ /// Right Label's Text
+ ///
+ var rightText: String? {
+ get {
+ return rightColumn.text
+ }
+ set {
+ rightColumn.text = newValue
+ }
+ }
+
+ // MARK: - Initialization
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ descriptionLabel.applyBodyStyle()
+ leftColumn.applyFootnoteStyle()
+ rightColumn.applyFootnoteStyle()
+ leftColumn.textColor = StyleManager.sectionTitleColor
+ rightColumn.textColor = StyleManager.sectionTitleColor
+ borderView.backgroundColor = StyleManager.wooGreyBorder
+ }
+}
+
+
+// MARK: - Public Methods
+//
+extension TopPerformersHeaderView {
+ func configure(descriptionText: String?, leftText: String?, rightText: String?) {
+ descriptionLabel.text = descriptionText
+ leftColumn.text = leftText
+ rightColumn.text = rightText
+ }
+}
diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/TopPerformersHeaderView.xib b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/TopPerformersHeaderView.xib
new file mode 100644
index 00000000000..6d1f180f14e
--- /dev/null
+++ b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/Cells/TopPerformersHeaderView.xib
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/NewOrdersViewController.swift b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/NewOrdersViewController.swift
index bc91a756980..dedd88dd425 100644
--- a/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/NewOrdersViewController.swift
+++ b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/NewOrdersViewController.swift
@@ -69,7 +69,6 @@ extension NewOrdersViewController {
private extension NewOrdersViewController {
func configureView() {
- view.backgroundColor = StyleManager.wooWhite
topBorder.backgroundColor = StyleManager.wooGreyBorder
bottomBorder.backgroundColor = StyleManager.wooGreyBorder
titleLabel.applyHeadlineStyle()
@@ -85,6 +84,9 @@ private extension NewOrdersViewController {
resultsController.onDidChangeContent = { [weak self] in
self?.updateNewOrdersIfNeeded()
}
+ resultsController.onDidResetContent = { [weak self] in
+ self?.updateNewOrdersIfNeeded()
+ }
try? resultsController.performFetch()
}
}
diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/TopPerformerDataViewController.swift b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/TopPerformerDataViewController.swift
new file mode 100644
index 00000000000..62bb54928a6
--- /dev/null
+++ b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/TopPerformerDataViewController.swift
@@ -0,0 +1,244 @@
+import UIKit
+import Yosemite
+import Charts
+import XLPagerTabStrip
+import CocoaLumberjack
+
+
+class TopPerformerDataViewController: UIViewController, IndicatorInfoProvider {
+
+ // MARK: - Properties
+
+ public let granularity: StatGranularity
+
+ @IBOutlet private weak var tableView: IntrinsicTableView!
+
+ /// ResultsController: Loads TopEarnerStats for the current granularity from the Storage Layer
+ ///
+ private lazy var resultsController: ResultsController = {
+ let storageManager = AppDelegate.shared.storageManager
+ let formattedDateString = StatsStore.buildDateString(from: Date(), with: granularity)
+ let predicate = NSPredicate(format: "granularity = %@ AND date = %@", granularity.rawValue, formattedDateString)
+ let descriptor = NSSortDescriptor(key: "date", ascending: true)
+
+ return ResultsController(storageManager: storageManager, matching: predicate, sortedBy: [descriptor])
+ }()
+
+ // MARK: - Computed Properties
+
+ private var topEarnerStats: TopEarnerStats? {
+ return resultsController.fetchedObjects.first
+ }
+
+ private var hasTopEarnerStatsItems: Bool {
+ return (topEarnerStats?.items?.count ?? 0) > 0
+ }
+
+ private var tabDescription: String {
+ switch granularity {
+ case .day:
+ return NSLocalizedString("Today", comment: "Top Performers section title - today")
+ case .week:
+ return NSLocalizedString("This Week", comment: "Top Performers section title - this week")
+ case .month:
+ return NSLocalizedString("This Month", comment: "Top Performers section title - this month")
+ case .year:
+ return NSLocalizedString("This Year", comment: "Top Performers section title - this year")
+ }
+ }
+
+ // MARK: - Initialization
+
+ /// Designated Initializer
+ ///
+ init(granularity: StatGranularity) {
+ self.granularity = granularity
+ super.init(nibName: type(of: self).nibName, bundle: nil)
+ }
+
+ /// NSCoder Conformance
+ ///
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) is not supported")
+ }
+
+ // MARK: - View Lifecycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ configureView()
+ configureTableView()
+ configureResultsController()
+ registerTableViewCells()
+ registerTableViewHeaderFooters()
+ }
+}
+
+
+// MARK: - Public Interface
+//
+extension TopPerformerDataViewController {
+
+ func syncTopPerformers(onCompletion: ((Error?) -> ())? = nil) {
+ guard let siteID = StoresManager.shared.sessionManager.defaultStoreID else {
+ onCompletion?(nil)
+ return
+ }
+
+ let action = StatsAction.retrieveTopEarnerStats(siteID: siteID, granularity: granularity, latestDateToInclude: Date()) { (error) in
+ if let error = error {
+ DDLogError("⛔️ Dashboard (Top Performers) — Error synchronizing top earner stats: \(error)")
+ }
+ }
+ StoresManager.shared.dispatch(action)
+ }
+}
+
+
+// MARK: - Configuration
+//
+private extension TopPerformerDataViewController {
+
+ func configureView() {
+ view.backgroundColor = StyleManager.tableViewBackgroundColor
+ }
+
+ func configureTableView() {
+ tableView.backgroundColor = StyleManager.tableViewBackgroundColor
+ tableView.separatorColor = StyleManager.cellSeparatorColor
+ tableView.allowsSelection = false
+ tableView.estimatedRowHeight = Constants.estimatedRowHeight
+ tableView.estimatedSectionHeaderHeight = Constants.estimatedSectionHeight
+ tableView.rowHeight = UITableViewAutomaticDimension
+ tableView.tableFooterView = Constants.emptyView
+ }
+
+ func configureResultsController() {
+ resultsController.onDidChangeContent = { [weak self] in
+ self?.tableView.reloadData()
+ }
+ resultsController.onDidResetContent = { [weak self] in
+ self?.tableView.reloadData()
+ }
+ try? resultsController.performFetch()
+ }
+
+ func registerTableViewCells() {
+ let cells = [ProductTableViewCell.self, NoPeriodDataTableViewCell.self]
+
+ for cell in cells {
+ tableView.register(cell.loadNib(), forCellReuseIdentifier: cell.reuseIdentifier)
+ }
+ }
+
+ func registerTableViewHeaderFooters() {
+ let headersAndFooters = [TopPerformersHeaderView.self]
+
+ for kind in headersAndFooters {
+ tableView.register(kind.loadNib(), forHeaderFooterViewReuseIdentifier: kind.reuseIdentifier)
+ }
+ }
+}
+
+
+// MARK: - IndicatorInfoProvider Conformance (Tab Bar)
+//
+extension TopPerformerDataViewController {
+ func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
+ return IndicatorInfo(title: tabDescription)
+ }
+}
+
+
+// MARK: - UITableViewDataSource Conformance
+//
+extension TopPerformerDataViewController: UITableViewDataSource {
+
+ func numberOfSections(in tableView: UITableView) -> Int {
+ return Constants.numberOfSections
+ }
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return numberOfRows()
+ }
+
+ func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+ guard let cell = tableView.dequeueReusableHeaderFooterView(withIdentifier: TopPerformersHeaderView.reuseIdentifier) as? TopPerformersHeaderView else {
+ fatalError()
+ }
+
+ cell.configure(descriptionText: Text.sectionDescription,
+ leftText: Text.sectionLeftColumn.uppercased(),
+ rightText: Text.sectionRightColumn.uppercased())
+ return cell
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ guard let statsItem = statsItem(at: indexPath) else {
+ return tableView.dequeueReusableCell(withIdentifier: NoPeriodDataTableViewCell.reuseIdentifier, for: indexPath)
+ }
+
+ guard let cell = tableView.dequeueReusableCell(withIdentifier: ProductTableViewCell.reuseIdentifier,
+ for: indexPath) as? ProductTableViewCell else {
+ fatalError()
+ }
+
+ cell.configure(statsItem)
+ return cell
+ }
+}
+
+
+// MARK: - UITableViewDelegate Conformance
+//
+extension TopPerformerDataViewController: UITableViewDelegate {
+
+ func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+ return UITableViewAutomaticDimension
+ }
+
+ func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
+ // iOS 11 table bug. Must return a tiny value to collapse `nil` or `empty` section headers.
+ return CGFloat.leastNonzeroMagnitude
+ }
+}
+
+
+// MARK: - Private Helpers
+//
+private extension TopPerformerDataViewController {
+
+ func statsItem(at indexPath: IndexPath) -> TopEarnerStatsItem? {
+ guard let topEarnerStatsItem = topEarnerStats?.items?.sorted(by: >)[safe: indexPath.row] else {
+ return nil
+ }
+
+ return topEarnerStatsItem
+ }
+
+ func numberOfRows() -> Int {
+ guard hasTopEarnerStatsItems, let itemCount = topEarnerStats?.items?.count else {
+ return Constants.emptyStateRowCount
+ }
+ return itemCount
+ }
+}
+
+
+// MARK: - Constants!
+//
+private extension TopPerformerDataViewController {
+ enum Text {
+ static let sectionDescription = NSLocalizedString("Gain insights into how products are performing on your store", comment: "Description for Top Performers section of My Store tab.")
+ static let sectionLeftColumn = NSLocalizedString("Product", comment: "Description for Top Performers left column header")
+ static let sectionRightColumn = NSLocalizedString("Total Spend", comment: "Description for Top Performers right column header")
+ }
+
+ enum Constants {
+ static let estimatedRowHeight = CGFloat(80)
+ static let estimatedSectionHeight = CGFloat(125)
+ static let numberOfSections = 1
+ static let emptyStateRowCount = 1
+ static let emptyView = UIView(frame: .zero)
+ }
+}
diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/TopPerformerDataViewController.xib b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/TopPerformerDataViewController.xib
new file mode 100644
index 00000000000..957b22fcb0b
--- /dev/null
+++ b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/TopPerformerDataViewController.xib
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/TopPerformersViewController.swift b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/TopPerformersViewController.swift
new file mode 100644
index 00000000000..3ce72c346dc
--- /dev/null
+++ b/WooCommerce/Classes/ViewRelated/Dashboard/MyStore/TopPerformersViewController.swift
@@ -0,0 +1,108 @@
+import UIKit
+import Yosemite
+import CocoaLumberjack
+import XLPagerTabStrip
+
+
+class TopPerformersViewController: ButtonBarPagerTabStripViewController {
+
+ // MARK: - Properties
+
+ @IBOutlet private weak var topBorder: UIView!
+ @IBOutlet private weak var middleBorder: UIView!
+ @IBOutlet private weak var headingLabel: PaddedLabel!
+
+ private var dataVCs = [TopPerformerDataViewController]()
+
+ // MARK: - View Lifecycle
+
+ required init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ }
+
+ override func viewDidLoad() {
+ configureDataViewControllers()
+ configureTabStrip()
+ // 👆 must be called before super.viewDidLoad()
+
+ super.viewDidLoad()
+ configureView()
+ }
+
+ // MARK: - PagerTabStripDataSource
+
+ override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
+ return dataVCs
+ }
+}
+
+
+// MARK: - Public Interface
+//
+extension TopPerformersViewController {
+
+ func syncTopPerformers() {
+ dataVCs.forEach { (vc) in
+ vc.syncTopPerformers()
+ }
+ }
+}
+
+
+// MARK: - User Interface Configuration
+//
+private extension TopPerformersViewController {
+
+ func configureView() {
+ view.backgroundColor = StyleManager.tableViewBackgroundColor
+ topBorder.backgroundColor = StyleManager.wooGreyBorder
+ middleBorder.backgroundColor = StyleManager.wooGreyBorder
+ headingLabel.applyFootnoteStyle()
+ headingLabel.textColor = StyleManager.sectionTitleColor
+ headingLabel.textInsets = Constants.headerLabelInsets
+ headingLabel.text = NSLocalizedString("Top Performers", comment: "Header label for Top Performers section of My Store tab.").uppercased()
+ }
+
+ func configureDataViewControllers() {
+ let dayVC = TopPerformerDataViewController(granularity: .day)
+ let weekVC = TopPerformerDataViewController(granularity: .week)
+ let monthVC = TopPerformerDataViewController(granularity: .month)
+ let yearVC = TopPerformerDataViewController(granularity: .year)
+
+ dataVCs.append(dayVC)
+ dataVCs.append(weekVC)
+ dataVCs.append(monthVC)
+ dataVCs.append(yearVC)
+ }
+
+ func configureTabStrip() {
+ settings.style.buttonBarBackgroundColor = StyleManager.wooWhite
+ settings.style.buttonBarItemBackgroundColor = StyleManager.wooWhite
+ settings.style.selectedBarBackgroundColor = StyleManager.wooCommerceBrandColor
+ settings.style.buttonBarItemFont = StyleManager.subheadlineFont
+ settings.style.selectedBarHeight = TabStrip.selectedBarHeight
+ settings.style.buttonBarItemTitleColor = StyleManager.defaultTextColor
+ settings.style.buttonBarItemsShouldFillAvailableWidth = false
+ settings.style.buttonBarItemLeftRightMargin = TabStrip.buttonLeftRightMargin
+
+ changeCurrentIndexProgressive = { (oldCell: ButtonBarViewCell?, newCell: ButtonBarViewCell?, progressPercentage: CGFloat, changeCurrentIndex: Bool, animated: Bool) -> Void in
+ guard changeCurrentIndex == true else { return }
+ oldCell?.label.textColor = StyleManager.defaultTextColor
+ newCell?.label.textColor = StyleManager.wooCommerceBrandColor
+ }
+ }
+}
+
+
+// MARK: - Constants!
+//
+private extension TopPerformersViewController {
+ enum Constants {
+ static let headerLabelInsets = UIEdgeInsets(top: 0, left: 14, bottom: 6, right: 14)
+ }
+
+ enum TabStrip {
+ static let buttonLeftRightMargin: CGFloat = 14.0
+ static let selectedBarHeight: CGFloat = 3.0
+ }
+}
diff --git a/WooCommerce/Classes/ViewRelated/ReusableViews/IntrinsicTableView.swift b/WooCommerce/Classes/ViewRelated/ReusableViews/IntrinsicTableView.swift
new file mode 100644
index 00000000000..9f225231a71
--- /dev/null
+++ b/WooCommerce/Classes/ViewRelated/ReusableViews/IntrinsicTableView.swift
@@ -0,0 +1,26 @@
+import Foundation
+import UIKit
+
+class IntrinsicTableView: UITableView {
+
+ override var contentSize:CGSize {
+ didSet {
+ invalidateIntrinsicContentSize()
+ }
+ }
+
+ override var intrinsicContentSize: CGSize {
+ self.layoutIfNeeded()
+ return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
+ }
+
+ override func reloadData() {
+ super.reloadData()
+ invalidateIntrinsicContentSize()
+ }
+
+ override func endUpdates() {
+ super.endUpdates()
+ invalidateIntrinsicContentSize()
+ }
+}
diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj
index a5d7286e6a0..ef3c68f1a39 100644
--- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj
+++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj
@@ -24,6 +24,10 @@
74036CC0211B882100E462C2 /* PeriodDataViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74036CBF211B882100E462C2 /* PeriodDataViewController.swift */; };
7403F7E220EC04070097198F /* OrderStatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7403F7E120EC04070097198F /* OrderStatusViewModel.swift */; };
7421344A210A323C00C13890 /* WooAnalyticsStat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74213449210A323C00C13890 /* WooAnalyticsStat.swift */; };
+ 74334F36214AB130006D6AC5 /* ProductTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74334F34214AB12F006D6AC5 /* ProductTableViewCell.swift */; };
+ 74334F37214AB130006D6AC5 /* ProductTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 74334F35214AB12F006D6AC5 /* ProductTableViewCell.xib */; };
+ 743EDD9F214B05350039071B /* TopEarnerStatsItem+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743EDD9E214B05350039071B /* TopEarnerStatsItem+Woo.swift */; };
+ 7441E1D221503F77004E6ECE /* IntrinsicTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7441E1D121503F77004E6ECE /* IntrinsicTableView.swift */; };
746791632108D7C0007CF1DC /* WooAnalyticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 746791622108D7C0007CF1DC /* WooAnalyticsTests.swift */; };
746791662108D87B007CF1DC /* MockupAnalyticsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 746791652108D87B007CF1DC /* MockupAnalyticsProvider.swift */; };
747AA0892107CEC60047A89B /* AnalyticsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 747AA0882107CEC60047A89B /* AnalyticsProvider.swift */; };
@@ -31,11 +35,18 @@
748C7780211E18A600814F2C /* OrderStats+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748C777F211E18A600814F2C /* OrderStats+Woo.swift */; };
748C7782211E294000814F2C /* Double+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748C7781211E294000814F2C /* Double+Woo.swift */; };
748C7784211E2D8400814F2C /* DoubleWooTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748C7783211E2D8400814F2C /* DoubleWooTests.swift */; };
+ 748D34DE214828DD00E21A2F /* TopPerformerDataViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 748D34DC214828DC00E21A2F /* TopPerformerDataViewController.xib */; };
+ 748D34DF214828DD00E21A2F /* TopPerformersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D34DD214828DC00E21A2F /* TopPerformersViewController.swift */; };
+ 748D34E12148291E00E21A2F /* TopPerformerDataViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D34E02148291E00E21A2F /* TopPerformerDataViewController.swift */; };
+ 7493BB8E2149852A003071A9 /* TopPerformersHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7493BB8C2149852A003071A9 /* TopPerformersHeaderView.swift */; };
+ 7493BB8F2149852A003071A9 /* TopPerformersHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7493BB8D2149852A003071A9 /* TopPerformersHeaderView.xib */; };
74A93A40212DC60B00C13E04 /* NewOrdersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A93A3F212DC60B00C13E04 /* NewOrdersViewController.swift */; };
74AAF6A5212A04A900C612B0 /* ChartMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74AAF6A4212A04A900C612B0 /* ChartMarker.swift */; };
74AFF2EA211B9B1B0038153A /* StoreStatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74AFF2E9211B9B1B0038153A /* StoreStatsViewController.swift */; };
74D0A5302139CF1300E2919F /* String+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D0A52F2139CF1300E2919F /* String+Helpers.swift */; };
74E0F441211C9AE600A79CCE /* PeriodDataViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 74E0F440211C9AE600A79CCE /* PeriodDataViewController.xib */; };
+ 74F9E9CD214C036400A3F2D2 /* NoPeriodDataTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 74F9E9CB214C036400A3F2D2 /* NoPeriodDataTableViewCell.xib */; };
+ 74F9E9CE214C036400A3F2D2 /* NoPeriodDataTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74F9E9CC214C036400A3F2D2 /* NoPeriodDataTableViewCell.swift */; };
86DBBB0BDEA3488E2BEBB314 /* Pods_WooCommerce.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BABE5E07DD787ECA6D2A76DE /* Pods_WooCommerce.framework */; };
93BCF01F20DC2CE200EBF7A1 /* bash_secrets.tpl in Resources */ = {isa = PBXBuildFile; fileRef = 93BCF01E20DC2CE200EBF7A1 /* bash_secrets.tpl */; };
B50911302049E27A007D25DC /* DashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509112D2049E27A007D25DC /* DashboardViewController.swift */; };
@@ -209,6 +220,10 @@
74036CBF211B882100E462C2 /* PeriodDataViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeriodDataViewController.swift; sourceTree = ""; };
7403F7E120EC04070097198F /* OrderStatusViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatusViewModel.swift; sourceTree = ""; };
74213449210A323C00C13890 /* WooAnalyticsStat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooAnalyticsStat.swift; sourceTree = ""; };
+ 74334F34214AB12F006D6AC5 /* ProductTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductTableViewCell.swift; sourceTree = ""; };
+ 74334F35214AB12F006D6AC5 /* ProductTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ProductTableViewCell.xib; sourceTree = ""; };
+ 743EDD9E214B05350039071B /* TopEarnerStatsItem+Woo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TopEarnerStatsItem+Woo.swift"; sourceTree = ""; };
+ 7441E1D121503F77004E6ECE /* IntrinsicTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntrinsicTableView.swift; sourceTree = ""; };
746791622108D7C0007CF1DC /* WooAnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooAnalyticsTests.swift; sourceTree = ""; };
746791652108D87B007CF1DC /* MockupAnalyticsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockupAnalyticsProvider.swift; sourceTree = ""; };
747AA0882107CEC60047A89B /* AnalyticsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsProvider.swift; sourceTree = ""; };
@@ -216,11 +231,18 @@
748C777F211E18A600814F2C /* OrderStats+Woo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderStats+Woo.swift"; sourceTree = ""; };
748C7781211E294000814F2C /* Double+Woo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Woo.swift"; sourceTree = ""; };
748C7783211E2D8400814F2C /* DoubleWooTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoubleWooTests.swift; sourceTree = ""; };
+ 748D34DC214828DC00E21A2F /* TopPerformerDataViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TopPerformerDataViewController.xib; sourceTree = ""; };
+ 748D34DD214828DC00E21A2F /* TopPerformersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopPerformersViewController.swift; sourceTree = ""; };
+ 748D34E02148291E00E21A2F /* TopPerformerDataViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopPerformerDataViewController.swift; sourceTree = ""; };
+ 7493BB8C2149852A003071A9 /* TopPerformersHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopPerformersHeaderView.swift; sourceTree = ""; };
+ 7493BB8D2149852A003071A9 /* TopPerformersHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TopPerformersHeaderView.xib; sourceTree = ""; };
74A93A3F212DC60B00C13E04 /* NewOrdersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewOrdersViewController.swift; sourceTree = ""; };
74AAF6A4212A04A900C612B0 /* ChartMarker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartMarker.swift; sourceTree = ""; };
74AFF2E9211B9B1B0038153A /* StoreStatsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreStatsViewController.swift; sourceTree = ""; };
74D0A52F2139CF1300E2919F /* String+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Helpers.swift"; sourceTree = ""; };
74E0F440211C9AE600A79CCE /* PeriodDataViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PeriodDataViewController.xib; sourceTree = ""; };
+ 74F9E9CB214C036400A3F2D2 /* NoPeriodDataTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NoPeriodDataTableViewCell.xib; sourceTree = ""; };
+ 74F9E9CC214C036400A3F2D2 /* NoPeriodDataTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoPeriodDataTableViewCell.swift; sourceTree = ""; };
8A659E65308A3D9DD79A95F9 /* Pods-WooCommerceTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WooCommerceTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-WooCommerceTests/Pods-WooCommerceTests.release.xcconfig"; sourceTree = ""; };
90AC1C0B391E04A837BDC64E /* Pods-WooCommerce.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WooCommerce.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-WooCommerce/Pods-WooCommerce.debug.xcconfig"; sourceTree = ""; };
93BCF01E20DC2CE200EBF7A1 /* bash_secrets.tpl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = bash_secrets.tpl; sourceTree = ""; };
@@ -390,11 +412,15 @@
74036CBE211B87FD00E462C2 /* MyStore */ = {
isa = PBXGroup;
children = (
+ 74F9E9CA214C034A00A3F2D2 /* Cells */,
74AAF6A4212A04A900C612B0 /* ChartMarker.swift */,
74A93A3F212DC60B00C13E04 /* NewOrdersViewController.swift */,
74036CBF211B882100E462C2 /* PeriodDataViewController.swift */,
74E0F440211C9AE600A79CCE /* PeriodDataViewController.xib */,
74AFF2E9211B9B1B0038153A /* StoreStatsViewController.swift */,
+ 748D34DD214828DC00E21A2F /* TopPerformersViewController.swift */,
+ 748D34E02148291E00E21A2F /* TopPerformerDataViewController.swift */,
+ 748D34DC214828DC00E21A2F /* TopPerformerDataViewController.xib */,
);
path = MyStore;
sourceTree = "";
@@ -421,6 +447,19 @@
path = Analytics;
sourceTree = "";
};
+ 74F9E9CA214C034A00A3F2D2 /* Cells */ = {
+ isa = PBXGroup;
+ children = (
+ 74F9E9CC214C036400A3F2D2 /* NoPeriodDataTableViewCell.swift */,
+ 74F9E9CB214C036400A3F2D2 /* NoPeriodDataTableViewCell.xib */,
+ 74334F34214AB12F006D6AC5 /* ProductTableViewCell.swift */,
+ 74334F35214AB12F006D6AC5 /* ProductTableViewCell.xib */,
+ 7493BB8C2149852A003071A9 /* TopPerformersHeaderView.swift */,
+ 7493BB8D2149852A003071A9 /* TopPerformersHeaderView.xib */,
+ );
+ path = Cells;
+ sourceTree = "";
+ };
88A44ABE866401E6DB03AC60 /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -587,6 +626,7 @@
748C777F211E18A600814F2C /* OrderStats+Woo.swift */,
B5E96B3721137AA100DF68D0 /* OrderStatus+Woo.swift */,
CE24BCD7212F25D4001CD12E /* StorageOrder+Woo.swift */,
+ 743EDD9E214B05350039071B /* TopEarnerStatsItem+Woo.swift */,
);
path = Model;
sourceTree = "";
@@ -780,6 +820,7 @@
B5BE75DA213F1D1E00909A14 /* OverlayMessageView.swift */,
B5BE75DC213F1D3D00909A14 /* OverlayMessageView.xib */,
B50BB4152141828F00AF0F3C /* FooterSpinnerView.swift */,
+ 7441E1D121503F77004E6ECE /* IntrinsicTableView.swift */,
);
path = ReusableViews;
sourceTree = "";
@@ -948,6 +989,7 @@
CE1EC8F020B8A408009762BF /* OrderNoteTableViewCell.xib in Resources */,
B55D4BFD20B5CDE700D7A50F /* replace_secrets.rb in Resources */,
B5BE75DD213F1D3D00909A14 /* OverlayMessageView.xib in Resources */,
+ 748D34DE214828DD00E21A2F /* TopPerformerDataViewController.xib in Resources */,
CEE006062077D1280079161F /* SummaryTableViewCell.xib in Resources */,
CE24BCD0212DE8A6001CD12E /* HeadlineLabelTableViewCell.xib in Resources */,
B559EBB020A0BF8F00836CD4 /* LICENSE in Resources */,
@@ -957,13 +999,16 @@
CE1CCB4720570A6C000EE3AC /* en.lproj in Resources */,
CE32B11620BF8779006FBCF4 /* ProductListTableViewCell.xib in Resources */,
B557652D20F6827900185843 /* StoreTableViewCell.xib in Resources */,
+ 7493BB8F2149852A003071A9 /* TopPerformersHeaderView.xib in Resources */,
CE1EC8C620B46819009762BF /* PaymentTableViewCell.xib in Resources */,
CE21B3E120FFC59700A259D5 /* ProductDetailsTableViewCell.xib in Resources */,
CE85FD5320F677770080B73E /* Dashboard.storyboard in Resources */,
B56DB3CF2049BFAA00D4AA8E /* Main.storyboard in Resources */,
CE21B3D820FE669A00A259D5 /* BasicDisclosureTableViewCell.xib in Resources */,
+ 74334F37214AB130006D6AC5 /* ProductTableViewCell.xib in Resources */,
CE1EC8C820B478B6009762BF /* TwoColumnLabelView.xib in Resources */,
CE855365209BA6A700938BDC /* CustomerInfoTableViewCell.xib in Resources */,
+ 74F9E9CD214C036400A3F2D2 /* NoPeriodDataTableViewCell.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1170,7 +1215,9 @@
B55D4C0620B6027200D7A50F /* AuthenticationManager.swift in Sources */,
CE85FD5A20F7A7640080B73E /* SettingsFooterView.swift in Sources */,
B5BE368E20BED80B00BE0A8C /* SafariViewController.swift in Sources */,
+ 74334F36214AB130006D6AC5 /* ProductTableViewCell.swift in Sources */,
CE855364209BA6A700938BDC /* ShowHideSectionFooter.swift in Sources */,
+ 7441E1D221503F77004E6ECE /* IntrinsicTableView.swift in Sources */,
B57C744A20F5649300EEFC87 /* EmptyStoresTableViewCell.swift in Sources */,
B56DB3CA2049BFAA00D4AA8E /* AppDelegate.swift in Sources */,
B5A82EE7210263460053ADC8 /* UIViewController+Helpers.swift in Sources */,
@@ -1181,9 +1228,11 @@
CE24BCD8212F25D4001CD12E /* StorageOrder+Woo.swift in Sources */,
B557DA1520979904005962F4 /* CustomerNoteTableViewCell.swift in Sources */,
CE855366209BA6A700938BDC /* CustomerInfoTableViewCell.swift in Sources */,
+ 748D34E12148291E00E21A2F /* TopPerformerDataViewController.swift in Sources */,
B5A8532220BDBFAF00FAAB4D /* CircularImageView.swift in Sources */,
CE1F51252064179A00C6C810 /* UILabel+Helpers.swift in Sources */,
B54FBE552111F70700390F57 /* ResultsController+UIKit.swift in Sources */,
+ 74F9E9CE214C036400A3F2D2 /* NoPeriodDataTableViewCell.swift in Sources */,
B50911302049E27A007D25DC /* DashboardViewController.swift in Sources */,
B5D1AFB820BC510200DB0E8C /* UIImage+Woo.swift in Sources */,
CE1EC8E920B8A3F5009762BF /* OrderNoteViewModel.swift in Sources */,
@@ -1196,12 +1245,14 @@
CEE006052077D1280079161F /* SummaryTableViewCell.swift in Sources */,
CE1F51272064345B00C6C810 /* UIColor+Helpers.swift in Sources */,
B57B678C2107638C00AF8905 /* Order+Woo.swift in Sources */,
+ 7493BB8E2149852A003071A9 /* TopPerformersHeaderView.swift in Sources */,
CE1EC8CA20B479F1009762BF /* TwoColumnLabelView.swift in Sources */,
CE21B3DD20FF9BC200A259D5 /* ProductListViewController.swift in Sources */,
74AAF6A5212A04A900C612B0 /* ChartMarker.swift in Sources */,
CE32B11520BF8779006FBCF4 /* ProductListTableViewCell.swift in Sources */,
CE263DE8206ACE3E0015A693 /* MainTabBarController.swift in Sources */,
B5BE75DB213F1D1E00909A14 /* OverlayMessageView.swift in Sources */,
+ 743EDD9F214B05350039071B /* TopEarnerStatsItem+Woo.swift in Sources */,
CE1EC8EC20B8A3FF009762BF /* LeftImageTableViewCell.swift in Sources */,
74A93A40212DC60B00C13E04 /* NewOrdersViewController.swift in Sources */,
B57C744520F55BA600EEFC87 /* NSObject+Helpers.swift in Sources */,
@@ -1215,6 +1266,7 @@
B582F95920FFCEAA0060934A /* UITableViewHeaderFooterView+Helpers.swift in Sources */,
B5D1AFBA20BC515600DB0E8C /* UIColor+Woo.swift in Sources */,
CE1CCB4B20570B1F000EE3AC /* OrderListCell.swift in Sources */,
+ 748D34DF214828DD00E21A2F /* TopPerformersViewController.swift in Sources */,
B5AA7B3D20ED5D15004DA14F /* SessionManager.swift in Sources */,
CE24BCCF212DE8A6001CD12E /* HeadlineLabelTableViewCell.swift in Sources */,
B53B898D20D462A000EDB467 /* StoresManager.swift in Sources */,
diff --git a/Yosemite/Yosemite/Stores/StatsStore.swift b/Yosemite/Yosemite/Stores/StatsStore.swift
index e190214a0e9..0146aa4263d 100644
--- a/Yosemite/Yosemite/Stores/StatsStore.swift
+++ b/Yosemite/Yosemite/Stores/StatsStore.swift
@@ -33,6 +33,27 @@ public class StatsStore: Store {
}
+// MARK: - Public Helpers
+//
+public extension StatsStore {
+
+ /// Converts a Date into the appropriatly formatted string based on the `OrderStatGranularity`
+ ///
+ static func buildDateString(from date: Date, with granularity: StatGranularity) -> String {
+ switch granularity {
+ case .day:
+ return DateFormatter.Stats.statsDayFormatter.string(from: date)
+ case .week:
+ return DateFormatter.Stats.statsWeekFormatter.string(from: date)
+ case .month:
+ return DateFormatter.Stats.statsMonthFormatter.string(from: date)
+ case .year:
+ return DateFormatter.Stats.statsYearFormatter.string(from: date)
+ }
+ }
+}
+
+
// MARK: - Services!
//
private extension StatsStore {
@@ -42,7 +63,7 @@ private extension StatsStore {
func retrieveOrderStats(siteID: Int, granularity: StatGranularity, latestDateToInclude: Date, quantity: Int, onCompletion: @escaping (OrderStats?, Error?) -> Void) {
let remote = OrderStatsRemote(network: network)
- let formattedDateString = buildDateString(from: latestDateToInclude, with: granularity)
+ let formattedDateString = StatsStore.buildDateString(from: latestDateToInclude, with: granularity)
remote.loadOrderStats(for: siteID, unit: granularity, latestDateToInclude: formattedDateString, quantity: quantity) { (orderStats, error) in
guard let orderStats = orderStats else {
@@ -75,9 +96,9 @@ private extension StatsStore {
func retrieveTopEarnerStats(siteID: Int, granularity: StatGranularity, latestDateToInclude: Date, onCompletion: @escaping (Error?) -> Void) {
let remote = TopEarnersStatsRemote(network: network)
- let formattedDateString = buildDateString(from: latestDateToInclude, with: granularity)
+ let formattedDateString = StatsStore.buildDateString(from: latestDateToInclude, with: granularity)
- remote.loadTopEarnersStats(for: siteID, unit: granularity, latestDateToInclude: formattedDateString, limit: 5) { [weak self] (topEarnerStats, error) in
+ remote.loadTopEarnersStats(for: siteID, unit: granularity, latestDateToInclude: formattedDateString, limit: Constants.defaultTopEarnerStatsLimit) { [weak self] (topEarnerStats, error) in
guard let topEarnerStats = topEarnerStats else {
onCompletion(error)
return
@@ -87,21 +108,6 @@ private extension StatsStore {
onCompletion(nil)
}
}
-
- /// Converts a Date into the appropriatly formatted string based on the `OrderStatGranularity`
- ///
- func buildDateString(from date: Date, with granularity: StatGranularity) -> String {
- switch granularity {
- case .day:
- return DateFormatter.Stats.statsDayFormatter.string(from: date)
- case .week:
- return DateFormatter.Stats.statsWeekFormatter.string(from: date)
- case .month:
- return DateFormatter.Stats.statsMonthFormatter.string(from: date)
- case .year:
- return DateFormatter.Stats.statsYearFormatter.string(from: date)
- }
- }
}
@@ -140,3 +146,16 @@ extension StatsStore {
})
}
}
+
+
+// MARK: - Constants!
+//
+extension StatsStore {
+
+ enum Constants {
+
+ /// Default limit value for TopEarnerStats
+ ///
+ static let defaultTopEarnerStatsLimit: Int = 3
+ }
+}