jsdw_ios/QuickLocation/Section/Home/MemberAppUsageView.swift

321 lines
10 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// MemberAppUsageView.swift
// QuickLocation
//
import UIKit
struct MemberAppUsageItemPreview {
let rank: Int
let name: String
let tintHex: String
let durationText: String
let progress: CGFloat
}
struct MemberAppUsagePreview {
let totalTimeText: String
let segmentRatios: [CGFloat]
let segmentColors: [UIColor]
let topApps: [MemberAppUsageItemPreview]
static let mock = MemberAppUsagePreview(
totalTimeText: "24h 60m",
segmentRatios: [0.35, 0.28, 0.18, 0.19],
segmentColors: [
UIColor(hexStr: "#FF8FB1"),
UIColor(hexStr: "#FFD36E"),
UIColor(hexStr: "#7BE495"),
UIColor(hexStr: "#6EC1FF")
],
topApps: [
.init(rank: 1, name: "微信", tintHex: "#07C160", durationText: "1h 45m", progress: 1.0),
.init(rank: 2, name: "抖音", tintHex: "#111111", durationText: "1h 25m", progress: 0.76),
.init(rank: 3, name: "小红书", tintHex: "#FF2442", durationText: "45m", progress: 0.43),
.init(rank: 4, name: "美团", tintHex: "#FFC300", durationText: "30m", progress: 0.29),
.init(rank: 5, name: "Safari", tintHex: "#007AFF", durationText: "18m", progress: 0.17)
]
)
}
/// APP使 + TOP5
final class MemberAppUsageView: UIView {
private let titleColor = UIColor(hexStr: "#293445")
private let sectionIcon = UIImageView()
private let sectionTitleLab = UILabel()
private let cardView = UIView()
private let donutView = AppUsageDonutView()
private let totalTimeLab = UILabel()
private let totalHintLab = UILabel()
private let topTitleLab = UILabel()
private let topStack = UIStackView()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
configure(with: .mock)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with preview: MemberAppUsagePreview?) {
topStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
guard let preview else {
totalTimeLab.text = "--"
donutView.setSegments(ratios: [], colors: [])
return
}
totalTimeLab.text = preview.totalTimeText
donutView.setSegments(ratios: preview.segmentRatios, colors: preview.segmentColors)
for item in preview.topApps {
let row = AppUsageTopRowView()
row.configure(with: item)
topStack.addArrangedSubview(row)
}
}
private func setupUI() {
backgroundColor = .clear
sectionIcon.image = UIImage(named: "Home/member_app_usage")
sectionIcon.contentMode = .scaleAspectFit
addSubview(sectionIcon)
sectionIcon.layoutChain
.top()
.left(15)
.width(18)
.heightToWidth(1)
sectionTitleLab.text = "APP使用记录"
sectionTitleLab.font = .systemFont(ofSize: 16, weight: .bold)
sectionTitleLab.textColor = titleColor
addSubview(sectionTitleLab)
sectionTitleLab.layoutChain
.leftToRightOfView(sectionIcon, offset: 6)
.centerY(sectionIcon)
cardView.backgroundColor = .white
cardView.cornerRadius = 16
addSubview(cardView)
cardView.layoutChain
.topToBottomOfView(sectionIcon, offset: 12)
.edgesHorzontal(15)
.height(220)
.bottom()
cardView.addSubview(donutView)
donutView.layoutChain
.left(12)
.centerY()
.width(110)
.height(110)
totalTimeLab.font = FontManager.youSheBiaoTiHei(14)
totalTimeLab.textColor = titleColor
totalTimeLab.textAlignment = .center
cardView.addSubview(totalTimeLab)
totalTimeLab.layoutChain
.centerX(donutView)
.centerY(donutView, offset: -6)
totalHintLab.text = "今日总计"
totalHintLab.font = .systemFont(ofSize: 9, weight: .medium)
totalHintLab.textColor = UIColor(hexStr: "#767676")
totalHintLab.textAlignment = .center
cardView.addSubview(totalHintLab)
totalHintLab.layoutChain
.centerX(donutView)
.topToBottomOfView(totalTimeLab, offset: 2)
topTitleLab.text = "最爱使用TOP5"
topTitleLab.font = .systemFont(ofSize: 12, weight: .bold)
topTitleLab.textColor = titleColor
cardView.addSubview(topTitleLab)
topTitleLab.layoutChain
.top(14)
.leftToRightOfView(donutView, offset: 16)
.right(12)
topStack.axis = .vertical
topStack.spacing = 10
topStack.alignment = .fill
cardView.addSubview(topStack)
topStack.layoutChain
.topToBottomOfView(topTitleLab, offset: 10)
.leftToRightOfView(donutView, offset: 16)
.right(12)
.bottom(14)
}
}
// MARK: -
private final class AppUsageDonutView: UIView {
private var segmentLayers: [CAShapeLayer] = []
private var ratios: [CGFloat] = []
private var colors: [UIColor] = []
private let lineWidth: CGFloat = 14
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setSegments(ratios: [CGFloat], colors: [UIColor]) {
self.ratios = ratios
self.colors = colors
redraw()
}
private func redraw() {
segmentLayers.forEach { $0.removeFromSuperlayer() }
segmentLayers.removeAll()
guard !ratios.isEmpty, bounds.width > 0 else { return }
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = min(bounds.width, bounds.height) / 2 - lineWidth / 2
var startAngle = -CGFloat.pi / 2
let total = max(ratios.reduce(0, +), 0.001)
for (i, ratio) in ratios.enumerated() {
let sweep = (ratio / total) * CGFloat.pi * 2
guard sweep > 0.001 else { continue }
let path = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: startAngle,
endAngle: startAngle + sweep,
clockwise: true)
let layer = CAShapeLayer()
layer.path = path.cgPath
layer.strokeColor = colors[min(i, colors.count - 1)].cgColor
layer.fillColor = UIColor.clear.cgColor
layer.lineWidth = lineWidth
layer.lineCap = .butt
self.layer.addSublayer(layer)
segmentLayers.append(layer)
startAngle += sweep
}
}
override func layoutSubviews() {
super.layoutSubviews()
redraw()
}
}
// MARK: - TOP
private final class AppUsageTopRowView: UIView {
private var progressRatio: CGFloat = 0
private var progressWidthConstraint: NSLayoutConstraint?
private let rankLab = UILabel()
private let iconView = UIView()
private let iconLabel = UILabel()
private let nameLab = UILabel()
private let progressTrack = UIView()
private let progressFill = UIView()
private let durationLab = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with item: MemberAppUsageItemPreview) {
rankLab.text = "\(item.rank)"
nameLab.text = item.name
durationLab.text = item.durationText
iconView.backgroundColor = UIColor(hexStr: item.tintHex)
iconLabel.text = String(item.name.prefix(1))
progressRatio = max(0.06, min(1, item.progress))
setNeedsLayout()
}
private func setupUI() {
rankLab.font = .systemFont(ofSize: 10, weight: .bold)
rankLab.textColor = UIColor(hexStr: "#353B4F")
rankLab.textAlignment = .center
addSubview(rankLab)
rankLab.layoutChain
.left()
.centerY()
.width(14)
iconView.cornerRadius = 8
addSubview(iconView)
iconView.layoutChain
.leftToRightOfView(rankLab, offset: 4)
.centerY()
.width(16)
.height(16)
iconLabel.font = .systemFont(ofSize: 9, weight: .bold)
iconLabel.textColor = .white
iconLabel.textAlignment = .center
iconView.addSubview(iconLabel)
iconLabel.layoutChain.centerX().centerY()
durationLab.font = .systemFont(ofSize: 10, weight: .medium)
durationLab.textColor = UIColor(hexStr: "#767676")
durationLab.setContentCompressionResistancePriority(.required, for: .horizontal)
addSubview(durationLab)
durationLab.layoutChain
.right()
.centerY()
nameLab.font = .systemFont(ofSize: 11, weight: .medium)
nameLab.textColor = UIColor(hexStr: "#353B4F")
addSubview(nameLab)
nameLab.layoutChain
.leftToRightOfView(iconView, offset: 6)
.top()
.rightToLeftOfView(durationLab, offset: -6)
progressTrack.backgroundColor = UIColor(hexStr: "#F0F0F0")
progressTrack.cornerRadius = 2
addSubview(progressTrack)
progressTrack.layoutChain
.leftToView(nameLab)
.rightToView(durationLab)
.topToBottomOfView(nameLab, offset: 4)
.height(4)
.bottom()
progressFill.backgroundColor = UIColor(hexStr: "#58EDFF")
progressFill.cornerRadius = 2
progressTrack.addSubview(progressFill)
progressFill.layoutChain
.left()
.edgesVertical()
progressWidthConstraint = progressFill.widthAnchor.constraint(equalTo: progressTrack.widthAnchor, multiplier: 0)
progressWidthConstraint?.isActive = true
}
override func layoutSubviews() {
super.layoutSubviews()
progressWidthConstraint?.isActive = false
progressWidthConstraint = progressFill.widthAnchor.constraint(
equalTo: progressTrack.widthAnchor,
multiplier: progressRatio
)
progressWidthConstraint?.isActive = true
}
}