Show six labeled activity months

This commit is contained in:
Georg Bauer
2026-07-31 10:52:40 +02:00
parent 201f32f125
commit 82fadda983
5 changed files with 97 additions and 15 deletions

View File

@@ -1479,13 +1479,17 @@ public struct HeatCell: Equatable, Hashable {
public var week: UInt32
public var day: UInt32
public var level: UInt32
public var timestamp: Int64
public var contributions: Int64
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(week: UInt32, day: UInt32, level: UInt32) {
public init(week: UInt32, day: UInt32, level: UInt32, timestamp: Int64, contributions: Int64) {
self.week = week
self.day = day
self.level = level
self.timestamp = timestamp
self.contributions = contributions
}
@@ -1506,7 +1510,9 @@ public struct FfiConverterTypeHeatCell: FfiConverterRustBuffer {
try HeatCell(
week: FfiConverterUInt32.read(from: &buf),
day: FfiConverterUInt32.read(from: &buf),
level: FfiConverterUInt32.read(from: &buf)
level: FfiConverterUInt32.read(from: &buf),
timestamp: FfiConverterInt64.read(from: &buf),
contributions: FfiConverterInt64.read(from: &buf)
)
}
@@ -1514,6 +1520,8 @@ public struct FfiConverterTypeHeatCell: FfiConverterRustBuffer {
FfiConverterUInt32.write(value.week, into: &buf)
FfiConverterUInt32.write(value.day, into: &buf)
FfiConverterUInt32.write(value.level, into: &buf)
FfiConverterInt64.write(value.timestamp, into: &buf)
FfiConverterInt64.write(value.contributions, into: &buf)
}
}
@@ -2855,4 +2863,4 @@ public func uniffiEnsureGotchaCoreInitialized() {
}
}
// swiftlint:enable all
// swiftlint:enable all

View File

@@ -403,18 +403,32 @@ final class HomeViewController: RefreshingTableViewController {
final class HeatmapView: UIView {
private let cells: [HeatCell]
private let calendar = Calendar(identifier: .gregorian)
init(page: HomePage) {
cells = page.heatCells
let latest = page.heatCells.last.map {
Date(timeIntervalSince1970: TimeInterval($0.timestamp))
} ?? Date()
let latestMonth = Calendar(identifier: .gregorian).date(
from: Calendar(identifier: .gregorian).dateComponents([.year, .month], from: latest)
) ?? latest
let firstMonth = Calendar(identifier: .gregorian).date(
byAdding: .month,
value: -5,
to: latestMonth
) ?? latestMonth
cells = page.heatCells.filter {
Date(timeIntervalSince1970: TimeInterval($0.timestamp)) >= firstMonth
}
super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 132))
backgroundColor = .systemBackground
let title = UILabel(frame: CGRect(x: 16, y: 12, width: 300, height: 22))
title.text = "Activity · last 12 months"
title.text = "Activity · last 6 months"
title.font = .preferredFont(forTextStyle: .subheadline)
title.textColor = .secondaryLabel
addSubview(title)
let total = UILabel(frame: CGRect(x: 16, y: 104, width: 300, height: 18))
total.text = "\(page.contributionCount) contributions"
let total = UILabel(frame: CGRect(x: 16, y: 108, width: 300, height: 18))
total.text = "\(cells.reduce(0) { $0 + $1.contributions }) contributions"
total.font = .preferredFont(forTextStyle: .caption1)
total.textColor = .tertiaryLabel
addSubview(total)
@@ -424,9 +438,51 @@ final class HeatmapView: UIView {
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
override func draw(_ rect: CGRect) {
let width = max(4, min(6, (bounds.width - 32) / 55))
guard
let first = cells.first,
let last = cells.last
else { return }
let firstDate = Date(timeIntervalSince1970: TimeInterval(first.timestamp))
let lastDate = Date(timeIntervalSince1970: TimeInterval(last.timestamp))
guard let firstWeek = calendar.dateInterval(of: .weekOfYear, for: firstDate)?.start else {
return
}
let monthFormatter = DateFormatter()
monthFormatter.dateFormat = "MMM"
let months = cells.reduce(into: [Date]()) { result, cell in
let date = Date(timeIntervalSince1970: TimeInterval(cell.timestamp))
let month = calendar.date(
from: calendar.dateComponents([.year, .month], from: date)
) ?? date
if result.last != month { result.append(month) }
}
let weekCount = CGFloat(
(calendar.dateComponents([.day], from: firstWeek, to: lastDate).day ?? 0) / 7 + 1
)
let monthGap: CGFloat = 4
let width = max(4, min(6, (bounds.width - 32) / weekCount - 1))
let gap = width + 1
let labelAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.preferredFont(forTextStyle: .caption2),
.foregroundColor: UIColor.secondaryLabel,
]
for (index, month) in months.enumerated() {
let days = calendar.dateComponents([.day], from: firstWeek, to: month).day ?? 0
monthFormatter.string(from: month).draw(
at: CGPoint(
x: 16 + CGFloat(days / 7) * gap + CGFloat(index) * monthGap,
y: 34
),
withAttributes: labelAttributes
)
}
for cell in cells {
let date = Date(timeIntervalSince1970: TimeInterval(cell.timestamp))
let days = calendar.dateComponents([.day], from: firstWeek, to: date).day ?? 0
let month = calendar.date(
from: calendar.dateComponents([.year, .month], from: date)
) ?? date
let monthIndex = months.firstIndex(of: month) ?? 0
let colors: [UIColor] = [
.systemGray5,
UIColor(red: 0.72, green: 0.85, blue: 0.96, alpha: 1),
@@ -437,8 +493,8 @@ final class HeatmapView: UIView {
colors[Int(min(cell.level, 4))].setFill()
UIBezierPath(
roundedRect: CGRect(
x: 16 + CGFloat(cell.week) * gap,
y: 43 + CGFloat(cell.day) * gap,
x: 16 + CGFloat(days / 7) * gap + CGFloat(monthIndex) * monthGap,
y: 48 + CGFloat(calendar.component(.weekday, from: date) - 1) * gap,
width: width,
height: width
),