-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Stats: Create missing ghost (loading) views (#23146)
- Loading branch information
Showing
10 changed files
with
374 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostLineChartCell.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import UIKit | ||
import WordPressShared | ||
import DesignSystem | ||
|
||
final class StatsGhostLineChartCell: StatsGhostBaseCell { | ||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { | ||
super.init(style: style, reuseIdentifier: reuseIdentifier) | ||
|
||
let lineChart = StatsGhostLineChartView() | ||
lineChart.translatesAutoresizingMaskIntoConstraints = false | ||
contentView.addSubview(lineChart) | ||
topConstraint = lineChart.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0) | ||
topConstraint?.isActive = true | ||
NSLayoutConstraint.activate([ | ||
lineChart.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .DS.Padding.double), | ||
contentView.trailingAnchor.constraint(equalTo: lineChart.trailingAnchor, constant: .DS.Padding.double), | ||
contentView.bottomAnchor.constraint(equalTo: lineChart.bottomAnchor, constant: .DS.Padding.single), | ||
lineChart.heightAnchor.constraint(equalToConstant: 190), | ||
]) | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
} | ||
|
||
final class StatsGhostLineChartView: UIView { | ||
override init(frame: CGRect) { | ||
super.init(frame: frame) | ||
commonInit() | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
super.init(coder: coder) | ||
commonInit() | ||
} | ||
|
||
private func commonInit() { | ||
backgroundColor = .clear | ||
createMask() | ||
} | ||
|
||
private func createMask() { | ||
let maskLayer = CAShapeLayer() | ||
maskLayer.frame = bounds | ||
|
||
let path = UIBezierPath() | ||
path.move(to: CGPoint(x: 0, y: bounds.maxY)) | ||
|
||
let wavePoints = [ | ||
CGPoint(x: bounds.width * 0.1, y: bounds.maxY * 0.8), | ||
CGPoint(x: bounds.width * 0.3, y: bounds.maxY * 0.6), | ||
CGPoint(x: bounds.width * 0.5, y: bounds.maxY * 0.4), | ||
CGPoint(x: bounds.width * 0.7, y: bounds.maxY * 0.2), | ||
CGPoint(x: bounds.width * 0.9, y: bounds.maxY * 0.5), | ||
CGPoint(x: bounds.width, y: 0) | ||
] | ||
|
||
for (index, point) in wavePoints.enumerated() { | ||
if index == 0 { | ||
path.addLine(to: point) | ||
} else { | ||
let previousPoint = wavePoints[index - 1] | ||
let midPointX = (previousPoint.x + point.x) / 2 | ||
path.addCurve(to: point, controlPoint1: CGPoint(x: midPointX, y: previousPoint.y), controlPoint2: CGPoint(x: midPointX, y: point.y)) | ||
} | ||
} | ||
|
||
path.addLine(to: CGPoint(x: bounds.width, y: 0)) | ||
path.addLine(to: CGPoint(x: bounds.width, y: bounds.maxY)) | ||
path.addLine(to: CGPoint(x: 0, y: bounds.maxY)) | ||
path.close() | ||
|
||
maskLayer.path = path.cgPath | ||
maskLayer.fillColor = UIColor.white.cgColor | ||
maskLayer.fillRule = .evenOdd | ||
layer.mask = maskLayer | ||
backgroundColor = .clear | ||
} | ||
|
||
override func layoutSubviews() { | ||
super.layoutSubviews() | ||
layer.sublayers?.forEach { $0.frame = bounds } | ||
createMask() | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostSingleValueCell.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import UIKit | ||
import WordPressShared | ||
import DesignSystem | ||
|
||
final class StatsGhostSingleValueCell: StatsGhostBaseCell { | ||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { | ||
super.init(style: style, reuseIdentifier: reuseIdentifier) | ||
|
||
let ghostView = UIView() | ||
ghostView.translatesAutoresizingMaskIntoConstraints = false | ||
contentView.addSubview(ghostView) | ||
topConstraint = ghostView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: .DS.Padding.single) | ||
topConstraint?.isActive = true | ||
NSLayoutConstraint.activate([ | ||
ghostView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .DS.Padding.double), | ||
ghostView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -.DS.Padding.double), | ||
ghostView.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.35), | ||
ghostView.heightAnchor.constraint(equalToConstant: 36) | ||
]) | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
202 changes: 202 additions & 0 deletions
202
WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostTopCell.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
import UIKit | ||
import DesignSystem | ||
import WordPressUI | ||
|
||
final class StatsGhostTopCell: StatsGhostBaseCell, NibLoadable { | ||
private let titleHeader: UIView = { | ||
let header = UIView() | ||
header.translatesAutoresizingMaskIntoConstraints = false | ||
return header | ||
}() | ||
|
||
private let valueHeaders: UIStackView = { | ||
let headers = UIStackView() | ||
headers.translatesAutoresizingMaskIntoConstraints = false | ||
headers.spacing = .DS.Padding.double | ||
headers.distribution = .fill | ||
headers.alignment = .fill | ||
headers.axis = .horizontal | ||
return headers | ||
}() | ||
|
||
private let contentRows: StatsGhostTopCellRow = { | ||
let contentRows = StatsGhostTopCellRow() | ||
contentRows.translatesAutoresizingMaskIntoConstraints = false | ||
return contentRows | ||
}() | ||
|
||
var numberOfColumns: Int = 2 { | ||
didSet { | ||
configureCell(with: numberOfColumns) | ||
} | ||
} | ||
|
||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { | ||
super.init(style: style, reuseIdentifier: reuseIdentifier) | ||
|
||
contentView.addSubviews([titleHeader, valueHeaders, contentRows]) | ||
|
||
topConstraint = valueHeaders.topAnchor.constraint(equalTo: contentView.topAnchor, constant: .DS.Padding.single) | ||
topConstraint?.isActive = true | ||
NSLayoutConstraint.activate([ | ||
titleHeader.topAnchor.constraint(equalTo: valueHeaders.topAnchor), | ||
titleHeader.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .DS.Padding.double), | ||
titleHeader.widthAnchor.constraint(equalToConstant: 50), | ||
titleHeader.heightAnchor.constraint(equalToConstant: .DS.Padding.double), | ||
|
||
valueHeaders.heightAnchor.constraint(equalToConstant: .DS.Padding.double), | ||
valueHeaders.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -.DS.Padding.double), | ||
|
||
contentRows.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0), | ||
contentRows.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0), | ||
contentRows.topAnchor.constraint(equalTo: valueHeaders.bottomAnchor, constant: .DS.Padding.half), | ||
contentRows.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -.DS.Padding.single) | ||
]) | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
private func configureCell(with count: Int) { | ||
updateHeaders(count: count) | ||
contentRows.updateColumns(count: count) | ||
} | ||
|
||
private func updateHeaders(count: Int) { | ||
valueHeaders.removeAllSubviews() | ||
let headers = Array(repeating: UIView(), count: count-1) | ||
headers.forEach { header in | ||
configureHeader(header) | ||
valueHeaders.addArrangedSubview(header) | ||
} | ||
} | ||
|
||
private func configureHeader(_ header: UIView) { | ||
header.startGhostAnimation() | ||
header.widthAnchor.constraint(equalToConstant: Constants.columnWidth).isActive = true | ||
} | ||
} | ||
|
||
final class StatsGhostTopCellRow: UIView { | ||
private let avatarView = UIView() | ||
private let columnsStackView = createStackView() | ||
private let mainColumn = StatsGhostTopCellColumn() | ||
|
||
override init(frame: CGRect) { | ||
super.init(frame: frame) | ||
setupViews() | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
super.init(coder: coder) | ||
setupViews() | ||
} | ||
|
||
private static func createStackView() -> UIStackView { | ||
let stackView = UIStackView() | ||
stackView.alignment = .fill | ||
stackView.distribution = .fillEqually | ||
stackView.axis = .horizontal | ||
stackView.spacing = .DS.Padding.double | ||
return stackView | ||
} | ||
|
||
private func setupViews() { | ||
[columnsStackView, mainColumn, avatarView].forEach { | ||
$0.translatesAutoresizingMaskIntoConstraints = false | ||
addSubview($0) | ||
} | ||
setupConstraints() | ||
} | ||
|
||
private func setupConstraints() { | ||
NSLayoutConstraint.activate([ | ||
avatarView.heightAnchor.constraint(equalToConstant: .DS.Padding.medium), | ||
avatarView.widthAnchor.constraint(equalToConstant: .DS.Padding.medium), | ||
avatarView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: .DS.Padding.double), | ||
avatarView.topAnchor.constraint(equalTo: topAnchor, constant: .DS.Padding.double), | ||
avatarView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -.DS.Padding.double), | ||
mainColumn.leadingAnchor.constraint(equalTo: avatarView.trailingAnchor, constant: .DS.Padding.double), | ||
mainColumn.centerYAnchor.constraint(equalTo: centerYAnchor), | ||
columnsStackView.leadingAnchor.constraint(equalTo: mainColumn.trailingAnchor, constant: .DS.Padding.double), | ||
columnsStackView.centerYAnchor.constraint(equalTo: centerYAnchor), | ||
columnsStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -.DS.Padding.double) | ||
]) | ||
} | ||
|
||
func updateColumns(count: Int) { | ||
columnsStackView.removeAllSubviews() | ||
mainColumn.isHidden = count <= 1 | ||
|
||
let columns = Array(repeating: StatsGhostTopCellColumn(width: StatsGhostTopCell.Constants.columnWidth), count: count-1) | ||
columns.forEach(columnsStackView.addArrangedSubview) | ||
} | ||
} | ||
|
||
private class StatsGhostTopCellColumn: UIView { | ||
private let topView = UIView() | ||
private let bottomView = UIView() | ||
private let width: CGFloat? | ||
|
||
init(width: CGFloat? = nil) { | ||
self.width = width | ||
super.init(frame: .zero) | ||
setupViews() | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
private func setupViews() { | ||
let stackView = createStackView() | ||
addSubview(stackView) | ||
pinSubviewToAllEdges(stackView) | ||
setupConstraints() | ||
} | ||
|
||
private func createStackView() -> UIStackView { | ||
let stackView = UIStackView(arrangedSubviews: [topView, bottomView]) | ||
stackView.axis = .vertical | ||
stackView.spacing = .DS.Padding.half | ||
stackView.alignment = .fill | ||
stackView.distribution = .equalSpacing | ||
stackView.translatesAutoresizingMaskIntoConstraints = false | ||
return stackView | ||
} | ||
|
||
private func setupConstraints() { | ||
var constraints = [ | ||
topView.heightAnchor.constraint(equalToConstant: .DS.Padding.medium), | ||
bottomView.heightAnchor.constraint(equalToConstant: .DS.Padding.double) | ||
] | ||
|
||
if let width = width { | ||
constraints += [ | ||
topView.widthAnchor.constraint(equalToConstant: width), | ||
bottomView.widthAnchor.constraint(equalToConstant: width) | ||
] | ||
} | ||
|
||
NSLayoutConstraint.activate(constraints) | ||
startAnimations() | ||
} | ||
|
||
private func startAnimations() { | ||
topView.startGhostAnimation(style: GhostCellStyle.muriel) | ||
bottomView.startGhostAnimation(style: GhostCellStyle.muriel) | ||
} | ||
|
||
override func tintColorDidChange() { | ||
super.tintColorDidChange() | ||
topView.restartGhostAnimation(style: GhostCellStyle.muriel) | ||
bottomView.restartGhostAnimation(style: GhostCellStyle.muriel) | ||
} | ||
} | ||
|
||
fileprivate extension StatsGhostTopCell { | ||
enum Constants { | ||
static let columnWidth: CGFloat = 60 | ||
} | ||
} |
Oops, something went wrong.