diff --git a/WordPress/Classes/Stores/StatsPeriodStore.swift b/WordPress/Classes/Stores/StatsPeriodStore.swift index 7251d25a2dc8..edbd7ed5b6b3 100644 --- a/WordPress/Classes/Stores/StatsPeriodStore.swift +++ b/WordPress/Classes/Stores/StatsPeriodStore.swift @@ -5,15 +5,15 @@ import WordPressComStatsiOS enum PeriodAction: Action { // Period overview - case receivedSummary(_ summary: StatsSummaryTimeIntervalData?) - case receivedPostsAndPages(_ postsAndPages: StatsTopPostsTimeIntervalData?) - case receivedPublished(_ published: StatsPublishedPostsTimeIntervalData?) - case receivedReferrers(_ referrers: StatsTopReferrersTimeIntervalData?) - case receivedClicks(_ clicks: StatsTopClicksTimeIntervalData?) - case receivedAuthors(_ authors: StatsTopAuthorsTimeIntervalData?) - case receivedSearchTerms(_ searchTerms: StatsSearchTermTimeIntervalData?) - case receivedVideos(_ videos: StatsTopVideosTimeIntervalData?) - case receivedCountries(_ countries: StatsTopCountryTimeIntervalData?) + case receivedSummary(_ summary: StatsSummaryTimeIntervalData?, _ error: Error?) + case receivedPostsAndPages(_ postsAndPages: StatsTopPostsTimeIntervalData?, _ error: Error?) + case receivedPublished(_ published: StatsPublishedPostsTimeIntervalData?, _ error: Error?) + case receivedReferrers(_ referrers: StatsTopReferrersTimeIntervalData?, _ error: Error?) + case receivedClicks(_ clicks: StatsTopClicksTimeIntervalData?, _ error: Error?) + case receivedAuthors(_ authors: StatsTopAuthorsTimeIntervalData?, _ error: Error?) + case receivedSearchTerms(_ searchTerms: StatsSearchTermTimeIntervalData?, _ error: Error?) + case receivedVideos(_ videos: StatsTopVideosTimeIntervalData?, _ error: Error?) + case receivedCountries(_ countries: StatsTopCountryTimeIntervalData?, _ error: Error?) case refreshPeriodOverviewData(date: Date, period: StatsPeriodUnit, forceRefresh: Bool) // Period details @@ -109,30 +109,39 @@ struct PeriodStoreState { var summary: StatsSummaryTimeIntervalData? var fetchingSummary = false + var fetchingSummaryHasFailed = false var topPostsAndPages: StatsTopPostsTimeIntervalData? var fetchingPostsAndPages = false + var fetchingPostsAndPagesHasFailed = false var topReferrers: StatsTopReferrersTimeIntervalData? var fetchingReferrers = false + var fetchingReferrersHasFailed = false var topClicks: StatsTopClicksTimeIntervalData? var fetchingClicks = false + var fetchingClicksHasFailed = false var topPublished: StatsPublishedPostsTimeIntervalData? var fetchingPublished = false + var fetchingPublishedHasFailed = false var topAuthors: StatsTopAuthorsTimeIntervalData? var fetchingAuthors = false + var fetchingAuthorsHasFailed = false var topSearchTerms: StatsSearchTermTimeIntervalData? var fetchingSearchTerms = false + var fetchingSearchTermsHasFailed = false var topCountries: StatsTopCountryTimeIntervalData? var fetchingCountries = false + var fetchingCountriesHasFailed = false var topVideos: StatsTopVideosTimeIntervalData? var fetchingVideos = false + var fetchingVideosHasFailed = false // Post Stats @@ -141,6 +150,8 @@ struct PeriodStoreState { } class StatsPeriodStore: QueryStore { + var fetchingOverviewListener: ((_ fetching: Bool, _ success: Bool) -> Void)? + var cachedDataListener: ((_ hasCachedData: Bool) -> Void)? private var statsServiceRemote: StatsServiceRemoteV2? @@ -155,24 +166,24 @@ class StatsPeriodStore: QueryStore { } switch periodAction { - case .receivedSummary(let summary): - receivedSummary(summary) - case .receivedPostsAndPages(let postsAndPages): - receivedPostsAndPages(postsAndPages) - case .receivedReferrers(let referrers): - receivedReferrers(referrers) - case .receivedClicks(let clicks): - receivedClicks(clicks) - case .receivedPublished(let published): - receivedPublished(published) - case .receivedAuthors(let authors): - receivedAuthors(authors) - case .receivedSearchTerms(let searchTerms): - receivedSearchTerms(searchTerms) - case .receivedVideos(let videos): - receivedVideos(videos) - case .receivedCountries(let countries): - receivedCountries(countries) + case .receivedSummary(let summary, let error): + receivedSummary(summary, error) + case .receivedPostsAndPages(let postsAndPages, let error): + receivedPostsAndPages(postsAndPages, error) + case .receivedReferrers(let referrers, let error): + receivedReferrers(referrers, error) + case .receivedClicks(let clicks, let error): + receivedClicks(clicks, error) + case .receivedPublished(let published, let error): + receivedPublished(published, error) + case .receivedAuthors(let authors, let error): + receivedAuthors(authors, error) + case .receivedSearchTerms(let searchTerms, let error): + receivedSearchTerms(searchTerms, error) + case .receivedVideos(let videos, let error): + receivedVideos(videos, error) + case .receivedCountries(let countries, let error): + receivedCountries(countries, error) case .refreshPeriodOverviewData(let date, let period, let forceRefresh): refreshPeriodOverviewData(date: date, period: period, forceRefresh: forceRefresh) case .refreshPostsAndPages(let date, let period): @@ -199,6 +210,7 @@ class StatsPeriodStore: QueryStore { if !isFetchingOverview { DDLogInfo("Stats: All fetching operations finished.") + fetchingOverviewListener?(false, fetchingOverviewHasFailed) } } @@ -227,7 +239,6 @@ class StatsPeriodStore: QueryStore { try? ContextManager.shared.mainContext.save() DDLogInfo("Stats: finished persisting Stats to disk.") } - } // MARK: - Private Methods @@ -304,6 +315,8 @@ private extension StatsPeriodStore { setAllAsFetchingOverview() + fetchingOverviewListener?(true, false) + statsRemote.getData(for: period, endingOn: date) { (summary: StatsSummaryTimeIntervalData?, error: Error?) in if error != nil { DDLogInfo("Error fetching summary: \(String(describing: error?.localizedDescription))") @@ -311,7 +324,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching summary.") - self.actionDispatcher.dispatch(PeriodAction.receivedSummary(summary)) + self.actionDispatcher.dispatch(PeriodAction.receivedSummary(summary, error)) } statsRemote.getData(for: period, endingOn: date) { (posts: StatsTopPostsTimeIntervalData?, error: Error?) in @@ -321,7 +334,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching posts.") - self.actionDispatcher.dispatch(PeriodAction.receivedPostsAndPages(posts)) + self.actionDispatcher.dispatch(PeriodAction.receivedPostsAndPages(posts, error)) } statsRemote.getData(for: period, endingOn: date) { (published: StatsPublishedPostsTimeIntervalData?, error: Error?) in @@ -331,7 +344,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching published.") - self.actionDispatcher.dispatch(PeriodAction.receivedPublished(published)) + self.actionDispatcher.dispatch(PeriodAction.receivedPublished(published, error)) } statsRemote.getData(for: period, endingOn: date) { (referrers: StatsTopReferrersTimeIntervalData?, error: Error?) in @@ -341,7 +354,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching referrers.") - self.actionDispatcher.dispatch(PeriodAction.receivedReferrers(referrers)) + self.actionDispatcher.dispatch(PeriodAction.receivedReferrers(referrers, error)) } statsRemote.getData(for: period, endingOn: date) { (clicks: StatsTopClicksTimeIntervalData?, error: Error?) in @@ -351,7 +364,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching clicks.") - self.actionDispatcher.dispatch(PeriodAction.receivedClicks(clicks)) + self.actionDispatcher.dispatch(PeriodAction.receivedClicks(clicks, error)) } statsRemote.getData(for: period, endingOn: date) { (authors: StatsTopAuthorsTimeIntervalData?, error: Error?) in @@ -361,7 +374,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching authors.") - self.actionDispatcher.dispatch(PeriodAction.receivedAuthors(authors)) + self.actionDispatcher.dispatch(PeriodAction.receivedAuthors(authors, error)) } statsRemote.getData(for: period, endingOn: date) { (searchTerms: StatsSearchTermTimeIntervalData?, error: Error?) in @@ -371,7 +384,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching search terms.") - self.actionDispatcher.dispatch(PeriodAction.receivedSearchTerms(searchTerms)) + self.actionDispatcher.dispatch(PeriodAction.receivedSearchTerms(searchTerms, error)) } statsRemote.getData(for: period, endingOn: date) { (videos: StatsTopVideosTimeIntervalData?, error: Error?) in @@ -381,7 +394,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching videos.") - self.actionDispatcher.dispatch(PeriodAction.receivedVideos(videos)) + self.actionDispatcher.dispatch(PeriodAction.receivedVideos(videos, error)) } statsRemote.getData(for: period, endingOn: date) { (countries: StatsTopCountryTimeIntervalData?, error: Error?) in @@ -391,7 +404,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching countries.") - self.actionDispatcher.dispatch(PeriodAction.receivedCountries(countries)) + self.actionDispatcher.dispatch(PeriodAction.receivedCountries(countries, error)) } } @@ -427,6 +440,8 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished setting data to store from Core Data.") } + + cachedDataListener?(containsCachedData) } func refreshPeriodOverviewData(date: Date, period: StatsPeriodUnit, forceRefresh: Bool) { @@ -458,7 +473,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching all posts.") - self.actionDispatcher.dispatch(PeriodAction.receivedPostsAndPages(posts)) + self.actionDispatcher.dispatch(PeriodAction.receivedPostsAndPages(posts, error)) self.persistToCoreData() } } @@ -486,7 +501,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching all search terms.") - self.actionDispatcher.dispatch(PeriodAction.receivedSearchTerms(searchTerms)) + self.actionDispatcher.dispatch(PeriodAction.receivedSearchTerms(searchTerms, error)) self.persistToCoreData() } } @@ -514,7 +529,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching videos.") - self.actionDispatcher.dispatch(PeriodAction.receivedVideos(videos)) + self.actionDispatcher.dispatch(PeriodAction.receivedVideos(videos, error)) self.persistToCoreData() } } @@ -542,7 +557,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching all clicks.") - self.actionDispatcher.dispatch(PeriodAction.receivedClicks(clicks)) + self.actionDispatcher.dispatch(PeriodAction.receivedClicks(clicks, error)) self.persistToCoreData() } } @@ -570,7 +585,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching all authors.") - self.actionDispatcher.dispatch(PeriodAction.receivedAuthors(authors)) + self.actionDispatcher.dispatch(PeriodAction.receivedAuthors(authors, error)) self.persistToCoreData() } } @@ -598,7 +613,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching all referrers.") - self.actionDispatcher.dispatch(PeriodAction.receivedReferrers(referrers)) + self.actionDispatcher.dispatch(PeriodAction.receivedReferrers(referrers, error)) self.persistToCoreData() } } @@ -626,7 +641,7 @@ private extension StatsPeriodStore { DDLogInfo("Stats: Finished fetching all countries.") - self.actionDispatcher.dispatch(PeriodAction.receivedCountries(countries)) + self.actionDispatcher.dispatch(PeriodAction.receivedCountries(countries, error)) self.persistToCoreData() } } @@ -652,7 +667,7 @@ private extension StatsPeriodStore { DDLogInfo("Error fetching all Published: \(String(describing: error?.localizedDescription))") } DDLogInfo("Stats: Finished fetching all published.") - self.actionDispatcher.dispatch(PeriodAction.receivedPublished(published)) + self.actionDispatcher.dispatch(PeriodAction.receivedPublished(published, error)) self.persistToCoreData() } } @@ -692,9 +707,10 @@ private extension StatsPeriodStore { // MARK: - Receive data methods - func receivedSummary(_ summaryData: StatsSummaryTimeIntervalData?) { + func receivedSummary(_ summaryData: StatsSummaryTimeIntervalData?, _ error: Error?) { transaction { state in state.fetchingSummary = false + state.fetchingSummaryHasFailed = error != nil if summaryData != nil { state.summary = summaryData @@ -702,9 +718,10 @@ private extension StatsPeriodStore { } } - func receivedPostsAndPages(_ postsAndPages: StatsTopPostsTimeIntervalData?) { + func receivedPostsAndPages(_ postsAndPages: StatsTopPostsTimeIntervalData?, _ error: Error?) { transaction { state in state.fetchingPostsAndPages = false + state.fetchingPostsAndPagesHasFailed = error != nil if postsAndPages != nil { state.topPostsAndPages = postsAndPages @@ -712,9 +729,10 @@ private extension StatsPeriodStore { } } - func receivedReferrers(_ referrers: StatsTopReferrersTimeIntervalData?) { + func receivedReferrers(_ referrers: StatsTopReferrersTimeIntervalData?, _ error: Error?) { transaction { state in state.fetchingReferrers = false + state.fetchingReferrersHasFailed = error != nil if referrers != nil { state.topReferrers = referrers @@ -722,9 +740,10 @@ private extension StatsPeriodStore { } } - func receivedClicks(_ clicks: StatsTopClicksTimeIntervalData?) { + func receivedClicks(_ clicks: StatsTopClicksTimeIntervalData?, _ error: Error?) { transaction { state in state.fetchingClicks = false + state.fetchingClicksHasFailed = error != nil if clicks != nil { state.topClicks = clicks @@ -732,9 +751,10 @@ private extension StatsPeriodStore { } } - func receivedAuthors(_ authors: StatsTopAuthorsTimeIntervalData?) { + func receivedAuthors(_ authors: StatsTopAuthorsTimeIntervalData?, _ error: Error?) { transaction { state in state.fetchingAuthors = false + state.fetchingAuthorsHasFailed = error != nil if authors != nil { state.topAuthors = authors @@ -742,9 +762,10 @@ private extension StatsPeriodStore { } } - func receivedPublished(_ published: StatsPublishedPostsTimeIntervalData?) { + func receivedPublished(_ published: StatsPublishedPostsTimeIntervalData?, _ error: Error?) { transaction { state in state.fetchingPublished = false + state.fetchingPublishedHasFailed = error != nil if published != nil { state.topPublished = published @@ -752,9 +773,10 @@ private extension StatsPeriodStore { } } - func receivedSearchTerms(_ searchTerms: StatsSearchTermTimeIntervalData?) { + func receivedSearchTerms(_ searchTerms: StatsSearchTermTimeIntervalData?, _ error: Error?) { transaction { state in state.fetchingSearchTerms = false + state.fetchingSearchTermsHasFailed = error != nil if searchTerms != nil { state.topSearchTerms = searchTerms @@ -762,9 +784,10 @@ private extension StatsPeriodStore { } } - func receivedVideos(_ videos: StatsTopVideosTimeIntervalData?) { + func receivedVideos(_ videos: StatsTopVideosTimeIntervalData?, _ error: Error?) { transaction { state in state.fetchingVideos = false + state.fetchingVideosHasFailed = error != nil if videos != nil { state.topVideos = videos @@ -772,9 +795,10 @@ private extension StatsPeriodStore { } } - func receivedCountries(_ countries: StatsTopCountryTimeIntervalData?) { + func receivedCountries(_ countries: StatsTopCountryTimeIntervalData?, _ error: Error?) { transaction { state in state.fetchingCountries = false + state.fetchingCountriesHasFailed = error != nil if countries != nil { state.topCountries = countries @@ -828,6 +852,7 @@ private extension StatsPeriodStore { } func setAllAsFetchingOverview(fetching: Bool = true) { + state.fetchingSummary = fetching state.fetchingPostsAndPages = fetching state.fetchingReferrers = fetching state.fetchingClicks = fetching @@ -969,4 +994,31 @@ extension StatsPeriodStore { return state.fetchingPostStats } + var fetchingOverviewHasFailed: Bool { + return state.fetchingSummaryHasFailed && + state.fetchingPostsAndPagesHasFailed && + state.fetchingReferrersHasFailed && + state.fetchingClicksHasFailed && + state.fetchingPublishedHasFailed && + state.fetchingAuthorsHasFailed && + state.fetchingSearchTermsHasFailed && + state.fetchingVideosHasFailed && + state.fetchingCountriesHasFailed + } + + var containsCachedData: Bool { + if state.summary != nil && + state.topPostsAndPages != nil && + state.topReferrers != nil && + state.topClicks != nil && + state.topPublished != nil && + state.topAuthors != nil && + state.topSearchTerms != nil && + state.topCountries != nil && + state.topVideos != nil { + return true + } + + return false + } } diff --git a/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsInsightsTableViewController.swift b/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsInsightsTableViewController.swift index 549289241f91..540d2e360653 100644 --- a/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsInsightsTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsInsightsTableViewController.swift @@ -40,7 +40,7 @@ enum InsightType: Int { @objc optional func showPostStats(postID: Int, postTitle: String?, postURL: URL?) } -class SiteStatsInsightsTableViewController: UITableViewController, NoResultsViewHost { +class SiteStatsInsightsTableViewController: UITableViewController { // MARK: - Properties @@ -121,34 +121,6 @@ private extension SiteStatsInsightsTableViewController { TableFooterRow.self] } - func displayLoadingViewIfNecessary() { - guard tableHandler.viewModel.sections.isEmpty else { - return - } - - configureAndDisplayNoResults(on: tableView, - title: NoResultConstants.successTitle, - accessoryView: NoResultsViewController.loadingAccessoryView()) { [weak self] noResults in - noResults.delegate = self - noResults.hideImageView(false) - } - } - - func displayFailureViewIfNecessary() { - guard tableHandler.viewModel.sections.isEmpty else { - return - } - - updateNoResults(title: NoResultConstants.errorTitle, - subtitle: NoResultConstants.errorSubtitle, - buttonTitle: NoResultConstants.refreshButtonTitle) { [weak self] noResults in - let appDelegate = WordPressAppDelegate.shared - - noResults.delegate = self - noResults.hideImageView(appDelegate?.connectionAvailable ?? true) - } - } - // MARK: - Table Refreshing func refreshTableView() { @@ -210,8 +182,36 @@ private extension SiteStatsInsightsTableViewController { let insightTypesInt = insightsToShow.compactMap { $0.rawValue } UserDefaults.standard.set(insightTypesInt, forKey: userDefaultsKey) } +} + +extension SiteStatsInsightsTableViewController: NoResultsViewHost { + private func displayLoadingViewIfNecessary() { + guard tableHandler.viewModel.sections.isEmpty else { + return + } + + configureAndDisplayNoResults(on: tableView, + title: NoResultConstants.successTitle, + accessoryView: NoResultsViewController.loadingAccessoryView()) { [weak self] noResults in + noResults.delegate = self + noResults.hideImageView(false) + } + } + + private func displayFailureViewIfNecessary() { + guard tableHandler.viewModel.sections.isEmpty else { + return + } + + updateNoResults(title: NoResultConstants.errorTitle, + subtitle: NoResultConstants.errorSubtitle, + buttonTitle: NoResultConstants.refreshButtonTitle) { [weak self] noResults in + noResults.delegate = self + noResults.hideImageView() + } + } - enum NoResultConstants { + private enum NoResultConstants { static let successTitle = NSLocalizedString("Loading Stats...", comment: "The loading view title displayed while the service is loading") static let errorTitle = NSLocalizedString("Stats not loaded", comment: "The loading view title displayed when an error occurred") static let errorSubtitle = NSLocalizedString("There was a problem loading your data, refresh your page to try again.", comment: "The loading view subtitle displayed when an error occurred") diff --git a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift index adaebc43ebee..580360bdd94a 100644 --- a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift @@ -46,6 +46,8 @@ class SiteStatsPeriodTableViewController: UITableViewController { } else { refreshData() } + + displayLoadingViewIfNecessary() } } @@ -107,12 +109,32 @@ private extension SiteStatsPeriodTableViewController { periodDelegate: self) changeReceipt = viewModel?.onChange { [weak self] in - guard let store = self?.store, - !store.isFetchingOverview else { - return + self?.refreshTableView() + } + + viewModel?.overviewStoreStatusOnChange = { [weak self] status in + guard let self = self, + let viewModel = self.viewModel, + self.viewIsVisible() else { + return } - self?.refreshTableView() + self.tableHandler.viewModel = viewModel.tableViewModel() + + switch status { + case .fetchingData: + self.displayLoadingViewIfNecessary() + case .fetchingCacheData(let hasCache): + if hasCache { + self.hideNoResults() + } + case .fetchingDataCompleted(let error): + if error { + self.displayFailureViewIfNecessary() + } else { + self.hideNoResults() + } + } } } @@ -128,18 +150,14 @@ private extension SiteStatsPeriodTableViewController { // MARK: - Table Refreshing func refreshTableView() { - guard let viewModel = viewModel, - viewIsVisible() else { + viewIsVisible(), + !store.isFetchingOverview else { return } tableHandler.viewModel = viewModel.tableViewModel() refreshControl?.endRefreshing() - - // Scroll to the top of the table. - // TODO: look at removing this when loading view is added. - tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true) } @objc func userInitiatedRefresh() { @@ -179,6 +197,59 @@ private extension SiteStatsPeriodTableViewController { } +// MARK: - NoResultsViewHost + +extension SiteStatsPeriodTableViewController: NoResultsViewHost { + private func displayLoadingViewIfNecessary() { + guard tableHandler.viewModel.sections.isEmpty else { + return + } + + if noResultsViewController.view.superview != nil { + return + } + + configureAndDisplayNoResults(on: tableView, + title: NoResultConstants.successTitle, + accessoryView: NoResultsViewController.loadingAccessoryView()) { [weak self] noResults in + noResults.delegate = self + noResults.hideImageView(false) + } + } + + private func displayFailureViewIfNecessary() { + guard tableHandler.viewModel.sections.isEmpty else { + return + } + + updateNoResults(title: NoResultConstants.errorTitle, + subtitle: NoResultConstants.errorSubtitle, + buttonTitle: NoResultConstants.refreshButtonTitle) { [weak self] noResults in + noResults.delegate = self + noResults.hideImageView() + } + } + + private enum NoResultConstants { + static let successTitle = NSLocalizedString("Loading Stats...", comment: "The loading view title displayed while the service is loading") + static let errorTitle = NSLocalizedString("Stats not loaded", comment: "The loading view title displayed when an error occurred") + static let errorSubtitle = NSLocalizedString("There was a problem loading your data, refresh your page to try again.", comment: "The loading view subtitle displayed when an error occurred") + static let refreshButtonTitle = NSLocalizedString("Refresh", comment: "The loading view button title displayed when an error occurred") + } +} + +// MARK: - NoResultsViewControllerDelegate methods + +extension SiteStatsPeriodTableViewController: NoResultsViewControllerDelegate { + func actionButtonPressed() { + updateNoResults(title: NoResultConstants.successTitle, + accessoryView: NoResultsViewController.loadingAccessoryView()) { noResults in + noResults.hideImageView(false) + } + refreshData() + } +} + // MARK: - SiteStatsPeriodDelegate Methods extension SiteStatsPeriodTableViewController: SiteStatsPeriodDelegate { diff --git a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodViewModel.swift index 35d78aef024d..5c8d68a8a836 100644 --- a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodViewModel.swift @@ -9,6 +9,7 @@ class SiteStatsPeriodViewModel: Observable { // MARK: - Properties let changeDispatcher = Dispatcher() + var overviewStoreStatusOnChange: ((Status) -> Void)? private weak var periodDelegate: SiteStatsPeriodDelegate? private let store: StatsPeriodStore @@ -32,6 +33,15 @@ class SiteStatsPeriodViewModel: Observable { changeReceipt = store.onChange { [weak self] in self?.emitChange() } + + store.cachedDataListener = { [weak self] hasCacheData in + self?.overviewStoreStatusOnChange?(.fetchingCacheData(hasCacheData)) + } + + store.fetchingOverviewListener = { [weak self] fetching, success in + let status: Status = fetching ? .fetchingData : .fetchingDataCompleted(success) + self?.overviewStoreStatusOnChange?(status) + } } // MARK: - Table Model @@ -40,6 +50,11 @@ class SiteStatsPeriodViewModel: Observable { var tableRows = [ImmuTableRow]() + if !store.containsCachedData && + (store.fetchingOverviewHasFailed || store.isFetchingOverview) { + return ImmuTable(sections: []) + } + tableRows.append(contentsOf: overviewTableRows()) tableRows.append(contentsOf: postsAndPagesTableRows()) tableRows.append(contentsOf: referrersTableRows()) @@ -63,6 +78,14 @@ class SiteStatsPeriodViewModel: Observable { ActionDispatcher.dispatch(PeriodAction.refreshPeriodOverviewData(date: date, period: period, forceRefresh: true)) self.lastRequestedPeriod = period } + + // MARK: - State + + enum Status { + case fetchingData + case fetchingCacheData(_ hasCachedData: Bool) + case fetchingDataCompleted(_ success: Bool) + } } // MARK: - Private Extension