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 + } +}