Skip to content

Commit

Permalink
Merge pull request #18480 from wordpress-mobile/feature/stats-revamp-…
Browse files Browse the repository at this point in the history
…no-data

Stats Revamp: Adds no data states to Most Popular Time and Latest Post cells
  • Loading branch information
frosty authored May 3, 2022
2 parents b0eb5e5 + 4f01d65 commit 08f36c6
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import UIKit
import Gridicons


class StatsLatestPostSummaryInsightsCell: StatsBaseCell, LatestPostSummaryConfigurable {
Expand All @@ -8,14 +9,19 @@ class StatsLatestPostSummaryInsightsCell: StatsBaseCell, LatestPostSummaryConfig
private var lastPostDetails: StatsPostDetails?
private var postTitle = StatSection.noPostTitle

private var statsStackView: UIStackView!
private let outerStackView = UIStackView()
private let postStackView = UIStackView()
private let statsStackView = UIStackView()
private let postTitleLabel = UILabel()
private let postTimestampLabel = UILabel()
private let viewCountLabel = UILabel()
private let likeCountLabel = UILabel()
private let commentCountLabel = UILabel()
private let postImageView = CachedAnimatedImageView()

private let noDataLabel = UILabel()
private let createPostButton = UIButton(type: .system)

lazy var imageLoader: ImageLoader = {
return ImageLoader(imageView: postImageView, gifStrategy: .mediumGIFs)
}()
Expand All @@ -32,42 +38,46 @@ class StatsLatestPostSummaryInsightsCell: StatsBaseCell, LatestPostSummaryConfig
fatalError()
}

override func prepareForReuse() {
super.prepareForReuse()

toggleNoData(show: false)
}

// MARK: - View Configuration

private func configureView() {
let stackView = makeOuterStackView()
contentView.addSubview(stackView)
configureOuterStackView()
contentView.addSubview(outerStackView)

let postStackView = makePostStackView()
statsStackView = makeStatsStackView()
stackView.addArrangedSubview(postStackView)
stackView.addArrangedSubview(statsStackView)
configurePostStackView()
configureStatsStackView()
outerStackView.addArrangedSubview(postStackView)
outerStackView.addArrangedSubview(statsStackView)

topConstraint = stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: StatsBaseCell.Metrics.padding)
topConstraint = outerStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: StatsBaseCell.Metrics.padding)

NSLayoutConstraint.activate([
topConstraint,
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -StatsBaseCell.Metrics.padding),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: StatsBaseCell.Metrics.padding),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -StatsBaseCell.Metrics.padding),
outerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -StatsBaseCell.Metrics.padding),
outerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: StatsBaseCell.Metrics.padding),
outerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -StatsBaseCell.Metrics.padding),
])
}

private func makeOuterStackView() -> UIStackView {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = Metrics.outerStackViewSpacing
configureNoDataViews()
}

return stackView
private func configureOuterStackView() {
outerStackView.translatesAutoresizingMaskIntoConstraints = false
outerStackView.axis = .vertical
outerStackView.spacing = Metrics.outerStackViewSpacing
}

private func makePostStackView() -> UIStackView {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.alignment = .top
stackView.spacing = Metrics.postStackViewHorizontalSpacing
private func configurePostStackView() {
postStackView.translatesAutoresizingMaskIntoConstraints = false
postStackView.axis = .horizontal
postStackView.alignment = .top
postStackView.spacing = Metrics.postStackViewHorizontalSpacing

let postInfoStackView = UIStackView()
postInfoStackView.translatesAutoresizingMaskIntoConstraints = false
Expand All @@ -94,17 +104,14 @@ class StatsLatestPostSummaryInsightsCell: StatsBaseCell, LatestPostSummaryConfig
postImageView.layer.cornerRadius = Metrics.thumbnailCornerRadius
postImageView.layer.masksToBounds = true

stackView.addArrangedSubviews([postInfoStackView, postImageView])

return stackView
postStackView.addArrangedSubviews([postInfoStackView, postImageView])
}

private func makeStatsStackView() -> UIStackView {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.alignment = .top
stackView.spacing = Metrics.postStackViewHorizontalSpacing
private func configureStatsStackView() {
statsStackView.translatesAutoresizingMaskIntoConstraints = false
statsStackView.axis = .horizontal
statsStackView.alignment = .top
statsStackView.spacing = Metrics.postStackViewHorizontalSpacing

let viewsStack = makeVerticalStatsStackView(with: viewCountLabel, title: TextContent.views)
let likesStack = makeVerticalStatsStackView(with: likeCountLabel, title: TextContent.likes)
Expand All @@ -113,7 +120,7 @@ class StatsLatestPostSummaryInsightsCell: StatsBaseCell, LatestPostSummaryConfig
let divider1 = makeVerticalDivider()
let divider2 = makeVerticalDivider()

stackView.addArrangedSubviews([
statsStackView.addArrangedSubviews([
viewsStack,
divider1,
likesStack,
Expand All @@ -124,11 +131,9 @@ class StatsLatestPostSummaryInsightsCell: StatsBaseCell, LatestPostSummaryConfig
NSLayoutConstraint.activate([
viewsStack.widthAnchor.constraint(equalTo: likesStack.widthAnchor),
likesStack.widthAnchor.constraint(equalTo: commentsStack.widthAnchor),
divider1.heightAnchor.constraint(equalTo: stackView.heightAnchor),
divider2.heightAnchor.constraint(equalTo: stackView.heightAnchor)
divider1.heightAnchor.constraint(equalTo: statsStackView.heightAnchor),
divider2.heightAnchor.constraint(equalTo: statsStackView.heightAnchor)
])

return stackView
}

private func makeVerticalStatsStackView(with countLabel: UILabel, title: String) -> UIStackView {
Expand Down Expand Up @@ -162,14 +167,30 @@ class StatsLatestPostSummaryInsightsCell: StatsBaseCell, LatestPostSummaryConfig
return divider
}

private func configureNoDataViews() {
noDataLabel.font = .preferredFont(forTextStyle: .body)
noDataLabel.textColor = .textSubtle
noDataLabel.numberOfLines = 0
noDataLabel.text = TextContent.noData

createPostButton.setImage(.gridicon(.create), for: .normal)
createPostButton.setTitle(TextContent.createPost, for: .normal)

// Increase the padding between the image and title of the button
createPostButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: Metrics.createPostButtonInset, bottom: 0, right: -Metrics.createPostButtonInset)
createPostButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: Metrics.createPostButtonInset)

createPostButton.addTarget(self, action: #selector(createPostTapped), for: .touchUpInside)
}

// MARK: - Public Configuration

func configure(withInsightData lastPostInsight: StatsLastPostInsight?, chartData: StatsPostDetails?, andDelegate delegate: SiteStatsInsightsDelegate?) {
siteStatsInsightsDelegate = delegate
statSection = .insightsLatestPostSummary

guard let lastPostInsight = lastPostInsight else {
// Old cell shows Create Post if there's no latest post
toggleNoData(show: true)
return
}

Expand All @@ -186,6 +207,22 @@ class StatsLatestPostSummaryInsightsCell: StatsBaseCell, LatestPostSummaryConfig
commentCountLabel.text = lastPostInsight.commentsCount.abbreviatedString()
}

// Switches out the no data views into the main stack view if we have no data.
//
private func toggleNoData(show: Bool) {
if !show && outerStackView.subviews.contains(noDataLabel) {
noDataLabel.removeFromSuperview()
createPostButton.removeFromSuperview()
outerStackView.addArrangedSubviews([postStackView, statsStackView])
outerStackView.alignment = .fill
} else if show && outerStackView.subviews.contains(postStackView) {
postStackView.removeFromSuperview()
statsStackView.removeFromSuperview()
outerStackView.addArrangedSubviews([noDataLabel, createPostButton])
outerStackView.alignment = .leading
}
}

private func configureFeaturedImage(url: URL?) {
if let url = url,
let siteID = SiteStatsInformation.sharedInstance.siteID?.intValue,
Expand All @@ -202,17 +239,28 @@ class StatsLatestPostSummaryInsightsCell: StatsBaseCell, LatestPostSummaryConfig
}
}

// MARK: - Actions

@objc func createPostTapped() {
siteStatsInsightsDelegate?.showCreatePost?()
}

// MARK: - Constants

private enum Metrics {
static let outerStackViewSpacing: CGFloat = 16.0
static let postStackViewHorizontalSpacing: CGFloat = 16.0
static let postStackViewVerticalSpacing: CGFloat = 8.0
static let statsStackViewVerticalSpacing: CGFloat = 8.0
static let createPostButtonInset: CGFloat = 8.0
static let thumbnailSize: CGFloat = 68.0
static let thumbnailCornerRadius: CGFloat = 4.0
static let dividerWidth: CGFloat = 1.0
}

private enum TextContent {
static let noData = NSLocalizedString("stats.insights.latestPostSummary.noData", value: "You haven't published any posts yet. Check back later once you've published your first post!", comment: "Prompt shown in the 'Latest Post Summary' stats card if a user hasn't yet published anything.")
static let createPost = NSLocalizedString("stats.insights.latestPostSummary.createPost", value: "Create Post", comment: "Title of button shown in Stats prompting the user to create a post on their site.")
static let publishDate = NSLocalizedString("stats.insights.latestPostSummary.publishDate", value: "Published %@", comment: "Publish date of a post displayed in Stats. Placeholder will be replaced with a localized relative time, e.g. 2 days ago")
static let views = NSLocalizedString("stats.insights.latestPostSummary.views", value: "Views", comment: "Title for Views count in Latest Post Summary stats card.")
static let likes = NSLocalizedString("stats.insights.latestPostSummary.likes", value: "Likes", comment: "Title for Likes count in Latest Post Summary stats card.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ class StatsMostPopularTimeInsightsCell: StatsBaseCell {

// MARK: - Subviews

private var outerStackView: UIStackView!

private var noDataLabel: UILabel!

private var topLeftLabel: UILabel!
private var middleLeftLabel: UILabel!
private var bottomLeftLabel: UILabel!
Expand All @@ -27,20 +31,31 @@ class StatsMostPopularTimeInsightsCell: StatsBaseCell {
fatalError()
}

override func prepareForReuse() {
super.prepareForReuse()

displayNoData(show: false)
}

// MARK: - View Configuration

private func configureView() {
let stackView = makeOuterStackView()
contentView.addSubview(stackView)
outerStackView = makeOuterStackView()
contentView.addSubview(outerStackView)

topConstraint = stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: StatsBaseCell.Metrics.padding)
topConstraint = outerStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: StatsBaseCell.Metrics.padding)

NSLayoutConstraint.activate([
topConstraint,
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -StatsBaseCell.Metrics.padding),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: StatsBaseCell.Metrics.padding),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -StatsBaseCell.Metrics.padding),
outerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -StatsBaseCell.Metrics.padding),
outerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: StatsBaseCell.Metrics.padding),
outerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -StatsBaseCell.Metrics.padding),
])

noDataLabel = makeNoDataLabel()
contentView.addSubview(noDataLabel)
outerStackView.pinSubviewToAllEdges(noDataLabel)
noDataLabel.isHidden = true
}

private func makeOuterStackView() -> UIStackView {
Expand Down Expand Up @@ -112,6 +127,7 @@ class StatsMostPopularTimeInsightsCell: StatsBaseCell {
let middleLabel = UILabel()
middleLabel.textColor = .text
middleLabel.font = .preferredFont(forTextStyle: .title1).bold()
middleLabel.adjustsFontSizeToFitWidth = true

let bottomLabel = UILabel()
bottomLabel.textColor = .textSubtle
Expand All @@ -123,29 +139,52 @@ class StatsMostPopularTimeInsightsCell: StatsBaseCell {
return (topLabel: topLabel, middleLabel: middleLabel, bottomLabel: bottomLabel)
}

private func makeNoDataLabel() -> UILabel {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .body)
label.textColor = .textSubtle
label.numberOfLines = 0
label.text = TextContent.noData

return label
}

private func displayNoData(show: Bool) {
outerStackView.subviews.forEach({ $0.isHidden = show })
noDataLabel.isHidden = !show
}

// MARK: Public configuration

func configure(data: StatsMostPopularTimeData?, siteStatsInsightsDelegate: SiteStatsInsightsDelegate?) {
self.data = data
self.statSection = .insightsMostPopularTime
self.siteStatsInsightsDelegate = siteStatsInsightsDelegate

if let data = data {
topLeftLabel.text = data.mostPopularDayTitle
middleLeftLabel.text = data.mostPopularDay
bottomLeftLabel.text = data.dayPercentage

topRightLabel.text = data.mostPopularTimeTitle
middleRightLabel.text = data.mostPopularTime
bottomRightLabel.text = data.timePercentage
guard let data = data else {
displayNoData(show: true)
return
}

topLeftLabel.text = data.mostPopularDayTitle
middleLeftLabel.text = data.mostPopularDay
bottomLeftLabel.text = data.dayPercentage

topRightLabel.text = data.mostPopularTimeTitle
middleRightLabel.text = data.mostPopularTime
bottomRightLabel.text = data.timePercentage
}

private enum Metrics {
static let horizontalStackViewSpacing: CGFloat = 32.0
static let horizontalStackViewSpacing: CGFloat = 16.0
static let verticalStackViewSpacing: CGFloat = 8.0
static let dividerWidth: CGFloat = 1.0
}

private enum TextContent {
static let noData = NSLocalizedString("stats.insights.mostPopularTime.noData", value: "Not enough activity. Check back later when your site's had more visitors!", comment: "Hint displayed on the 'Most Popular Time' stats card when a user's site hasn't yet received enough traffic.")
}
}

// MARK: - Data / View Model
Expand Down

0 comments on commit 08f36c6

Please sign in to comment.