Workout streak banner: swipeable week / year / vs-last-week pages
Replaces the single lifetime-tally banner (which used a meaningless streak%7 tick bar) with a 3-page paged TabView: 1. This week — count + 7 per-day ticks for the current week 2. This year — count + 12 month ticks, with all-time total underneath 3. vs last week — this week's count and the delta (▲ more / ▼ fewer / same) Adds WorkoutViewModel breakdowns: workoutsThisWeek/LastWeek/ThisYear, currentWeekDays (7 flags), currentYearMonths (12 flags). (Not build-verified — sim runtime removed to reclaim disk; uses only APIs already present in the original banner.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -262,6 +262,58 @@ class WorkoutViewModel: ObservableObject {
|
||||
Set(workoutDates).count
|
||||
}
|
||||
|
||||
// MARK: - Streak-banner breakdowns (this week / this year / comparison)
|
||||
|
||||
/// Workout days in the week `offset` weeks from now (0 = current, -1 = last).
|
||||
private func workoutCount(inWeekOffset offset: Int) -> Int {
|
||||
let cal = Calendar.current
|
||||
guard let ref = cal.date(byAdding: .weekOfYear, value: offset, to: Date()),
|
||||
let wk = cal.dateInterval(of: .weekOfYear, for: ref) else { return 0 }
|
||||
let set = Set(workoutDates)
|
||||
return (0..<7).reduce(0) { acc, i in
|
||||
guard let d = cal.date(byAdding: .day, value: i, to: wk.start) else { return acc }
|
||||
return acc + (set.contains(iso.string(from: d)) ? 1 : 0)
|
||||
}
|
||||
}
|
||||
|
||||
var workoutsThisWeek: Int { workoutCount(inWeekOffset: 0) }
|
||||
var workoutsLastWeek: Int { workoutCount(inWeekOffset: -1) }
|
||||
|
||||
var workoutsThisYear: Int {
|
||||
let cal = Calendar.current
|
||||
let year = cal.component(.year, from: Date())
|
||||
return Set(workoutDates).filter {
|
||||
guard let d = iso.date(from: $0) else { return false }
|
||||
return cal.component(.year, from: d) == year
|
||||
}.count
|
||||
}
|
||||
|
||||
/// 7 flags for the current week (calendar week order) — true if worked out that day.
|
||||
var currentWeekDays: [Bool] {
|
||||
let cal = Calendar.current
|
||||
guard let wk = cal.dateInterval(of: .weekOfYear, for: Date()) else {
|
||||
return Array(repeating: false, count: 7)
|
||||
}
|
||||
let set = Set(workoutDates)
|
||||
return (0..<7).map { i in
|
||||
guard let d = cal.date(byAdding: .day, value: i, to: wk.start) else { return false }
|
||||
return set.contains(iso.string(from: d))
|
||||
}
|
||||
}
|
||||
|
||||
/// 12 flags for the current year — true if ≥1 workout that month.
|
||||
var currentYearMonths: [Bool] {
|
||||
let cal = Calendar.current
|
||||
let year = cal.component(.year, from: Date())
|
||||
var months = Array(repeating: false, count: 12)
|
||||
for s in workoutDates {
|
||||
guard let d = iso.date(from: s), cal.component(.year, from: d) == year else { continue }
|
||||
let m = cal.component(.month, from: d) - 1
|
||||
if (0..<12).contains(m) { months[m] = true }
|
||||
}
|
||||
return months
|
||||
}
|
||||
|
||||
private let iso: DateFormatter = {
|
||||
let f = DateFormatter(); f.dateFormat = "yyyy-MM-dd"; return f
|
||||
}()
|
||||
|
||||
Reference in New Issue
Block a user