Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stats: Create missing ghost (loading) views #23146

Merged
merged 13 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ class StatsGhostBaseCell: StatsBaseCell {

class StatsGhostGrowAudienceCell: StatsGhostBaseCell, NibLoadable { }
class StatsGhostTwoColumnCell: StatsGhostBaseCell, NibLoadable { }
class StatsGhostTopCell: StatsGhostBaseCell, NibLoadable { }
class StatsGhostTopHeaderCell: StatsGhostBaseCell, NibLoadable {
override func awakeFromNib() {
super.awakeFromNib()
Expand Down Expand Up @@ -115,3 +114,5 @@ fileprivate extension Date {
return days
}
}

class StatsGhostSingleValueCell: StatsGhostBaseCell, NibLoadable { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import UIKit
import WordPressShared

class StatsGhostLineChartCell: StatsGhostBaseCell, NibLoadable {
@IBOutlet private weak var lineChart: StatsGhostLineChartView!
}
staskus marked this conversation as resolved.
Show resolved Hide resolved

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()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="86" id="RFf-Q5-KXT" customClass="StatsGhostLineChartCell" customModule="WordPress" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="393" height="86"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" ambiguous="YES" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="RFf-Q5-KXT" id="Iiu-5M-T2y">
<rect key="frame" x="0.0" y="0.0" width="393" height="86"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hMK-XB-GNB" customClass="StatsGhostLineChartView" customModule="WordPress" customModuleProvider="target">
<rect key="frame" x="16" y="0.0" width="361" height="190"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="190" id="jkh-Kz-K4s"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="hMK-XB-GNB" firstAttribute="top" secondItem="Iiu-5M-T2y" secondAttribute="top" id="3de-Ll-1xU"/>
<constraint firstAttribute="trailing" secondItem="hMK-XB-GNB" secondAttribute="trailing" constant="16" id="J43-Si-G82"/>
<constraint firstItem="hMK-XB-GNB" firstAttribute="leading" secondItem="Iiu-5M-T2y" secondAttribute="leading" constant="16" id="lb0-tb-dVE"/>
<constraint firstAttribute="bottom" secondItem="hMK-XB-GNB" secondAttribute="bottom" constant="8" id="wJw-Gr-6b5"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="lineChart" destination="hMK-XB-GNB" id="UEm-lF-j4Q"/>
<outlet property="topConstraint" destination="3de-Ll-1xU" id="Qds-5i-X8F"/>
</connections>
<point key="canvasLocation" x="-241.98473282442748" y="-55.633802816901408"/>
</tableViewCell>
</objects>
</document>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="157" id="Ejr-ta-BUi" customClass="StatsGhostSingleValueCell" customModule="WordPress" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="567" height="157"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Ejr-ta-BUi" id="O06-iw-rma">
<rect key="frame" x="0.0" y="0.0" width="567" height="157"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5O3-uw-Zmm">
<rect key="frame" x="16" y="8" width="198.33333333333334" height="48"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="48" id="m6G-OU-4cD"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="5O3-uw-Zmm" firstAttribute="leading" secondItem="O06-iw-rma" secondAttribute="leading" constant="16" id="I44-ji-Ky4"/>
<constraint firstItem="5O3-uw-Zmm" firstAttribute="top" secondItem="O06-iw-rma" secondAttribute="top" constant="8" id="JmE-Io-eRp"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="5O3-uw-Zmm" secondAttribute="bottom" constant="24" id="X62-xv-jzD"/>
staskus marked this conversation as resolved.
Show resolved Hide resolved
<constraint firstItem="5O3-uw-Zmm" firstAttribute="width" secondItem="O06-iw-rma" secondAttribute="width" multiplier="0.35" id="aeQ-O4-Kux"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="topConstraint" destination="JmE-Io-eRp" id="ja5-ME-BCR"/>
</connections>
<point key="canvasLocation" x="-580.91603053435108" y="-485.56338028169017"/>
</tableViewCell>
</objects>
</document>
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ struct StatsGhostTopImmutableRow: StatsRowGhostable {

var hideTopBorder = false
var hideBottomBorder = false
var numberOfColumns: Int = 2
var statSection: StatSection? = nil

// MARK: - Hashable
Expand All @@ -66,6 +67,7 @@ struct StatsGhostTopImmutableRow: StatsRowGhostable {
detailCell.topBorder?.isHidden = hideTopBorder
detailCell.bottomBorder?.isHidden = hideBottomBorder
detailCell.statSection = statSection
detailCell.numberOfColumns = numberOfColumns
}
}
}
Expand Down Expand Up @@ -132,3 +134,19 @@ struct StatsGhostTitleRow: StatsRowGhostable {
enum GhostCellStyle {
static let muriel = GhostStyle(beatStartColor: .placeholderElement, beatEndColor: .placeholderElementFaded)
}

struct StatsGhostSingleValueRow: StatsRowGhostable {
let statSection: StatSection?

static let cell: ImmuTableCell = {
return ImmuTableCell.nib(StatsGhostSingleValueCell.defaultNib, StatsGhostSingleValueCell.self)
}()
}

struct StatsGhostLineChartRow: StatsRowGhostable {
let statSection: StatSection?

static let cell: ImmuTableCell = {
return ImmuTableCell.nib(StatsGhostLineChartCell.defaultNib, StatsGhostLineChartCell.self)
}()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import UIKit
import DesignSystem
import WordPressUI

final class StatsGhostTopCell: StatsGhostBaseCell, NibLoadable {
@IBOutlet private weak var topCellRow: StatsGhostTopCellRow!
@IBOutlet private weak var topCellHeaders: UIStackView!

var numberOfColumns: Int = 2 {
didSet {
configureCell(with: numberOfColumns)
}
}

private func configureCell(with count: Int) {
updateHeaders(count: count)
topCellRow.updateColumns(count: count)
}

private func updateHeaders(count: Int) {
topCellHeaders.removeAllSubviews()
let headers = Array(repeating: UIView(), count: count-1)
headers.forEach { header in
configureHeader(header)
topCellHeaders.addArrangedSubview(header)
}
}

private func configureHeader(_ header: UIView) {
header.startGhostAnimation()
header.widthAnchor.constraint(equalToConstant: Constants.columnWidth).isActive = true
}
}

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