Workout: per-day history + log KC-8/KC-9
- Add per-day workout history: WorkoutDayLog/LoggedExercise/LoggedSet models stored at kisani.workout.history.<uid> (iCloud-synced). - snapshotDay(_:) captures the active program's completed sets on full completion and before the daily reset; only days with >=1 done set. - New WorkoutHistoryView (clock button in the Workout header) lists past days newest-first with per-exercise breakdown. - Log KC-8 (auto-switch / Rest Day) and KC-9 (history) in ISSUES.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,7 @@ struct WorkoutView: View {
|
||||
@EnvironmentObject var vm: WorkoutViewModel
|
||||
@State private var showManage = false
|
||||
@State private var showAddSection = false
|
||||
@State private var showHistory = false
|
||||
@State private var isReordering = false
|
||||
@State private var trainAnyway = false // override the rest-day state for today
|
||||
@ObservedObject private var tm = TutorialManager.shared
|
||||
@@ -100,6 +101,7 @@ struct WorkoutView: View {
|
||||
.foregroundColor(AppColors.text3(cs))
|
||||
}
|
||||
Spacer()
|
||||
IButton(icon: "clock.arrow.circlepath") { showHistory = true }
|
||||
IButton(icon: "ellipsis") { showManage = true }
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
@@ -286,6 +288,11 @@ struct WorkoutView: View {
|
||||
.presentationDetents([.large])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
.sheet(isPresented: $showHistory) {
|
||||
WorkoutHistoryView().environmentObject(vm)
|
||||
.presentationDetents([.large])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
.sheet(isPresented: $showAddSection) {
|
||||
AddSectionSheet().environmentObject(vm)
|
||||
.presentationDetents([.height(240)])
|
||||
@@ -552,6 +559,77 @@ struct DotProgressCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Workout History
|
||||
struct WorkoutHistoryView: View {
|
||||
@EnvironmentObject var vm: WorkoutViewModel
|
||||
@Environment(\.colorScheme) var cs
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
private let parser: DateFormatter = {
|
||||
let f = DateFormatter(); f.dateFormat = "yyyy-MM-dd"; return f
|
||||
}()
|
||||
private let pretty: DateFormatter = {
|
||||
let f = DateFormatter(); f.dateFormat = "EEE, MMM d"; return f
|
||||
}()
|
||||
|
||||
private func label(_ key: String) -> String {
|
||||
guard let d = parser.date(from: key) else { return key }
|
||||
if Calendar.current.isDateInToday(d) { return "Today" }
|
||||
if Calendar.current.isDateInYesterday(d) { return "Yesterday" }
|
||||
return pretty.string(from: d)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
Text("Workout History").font(AppFonts.sans(17, weight: .bold)).foregroundColor(AppColors.text(cs))
|
||||
Spacer()
|
||||
Button("Done") { dismiss() }.font(AppFonts.sans(13, weight: .semibold)).foregroundColor(AppColors.accent).buttonStyle(.plain)
|
||||
}
|
||||
.padding(.horizontal, 20).padding(.top, 20).padding(.bottom, 14)
|
||||
Divider().background(AppColors.border(cs))
|
||||
|
||||
if vm.historySorted.isEmpty {
|
||||
VStack(spacing: 8) {
|
||||
Image(systemName: "clock.arrow.circlepath").font(.system(size: 28)).foregroundColor(AppColors.text3(cs))
|
||||
Text("No history yet").font(AppFonts.sans(14, weight: .semibold)).foregroundColor(AppColors.text2(cs))
|
||||
Text("Completed workouts are saved here each day.")
|
||||
.font(AppFonts.sans(11)).foregroundColor(AppColors.text3(cs)).multilineTextAlignment(.center)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity).padding()
|
||||
} else {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 12) {
|
||||
ForEach(vm.historySorted) { day in
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
HStack(spacing: 8) {
|
||||
ProgramIcon(name: day.programEmoji, size: 14, color: AppColors.accent)
|
||||
Text(day.programName).font(AppFonts.sans(14, weight: .semibold)).foregroundColor(AppColors.text(cs))
|
||||
Spacer()
|
||||
Text(label(day.date)).font(AppFonts.mono(10)).foregroundColor(AppColors.text3(cs))
|
||||
}
|
||||
Text("\(day.doneSets)/\(day.totalSets) sets")
|
||||
.font(AppFonts.mono(9, weight: .bold)).foregroundColor(AppColors.green)
|
||||
ForEach(day.exercises) { ex in
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: ex.sfSymbol).font(.system(size: 11)).foregroundColor(AppColors.text3(cs)).frame(width: 18)
|
||||
Text(ex.name).font(AppFonts.sans(12)).foregroundColor(AppColors.text2(cs))
|
||||
Spacer()
|
||||
Text("\(ex.doneSets)/\(ex.sets.count)").font(AppFonts.mono(10)).foregroundColor(AppColors.text3(cs))
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(14).cardStyle()
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16).padding(.top, 14).padding(.bottom, 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(AppColors.background(cs).ignoresSafeArea())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Rest Day Card
|
||||
struct RestDayCard: View {
|
||||
@Environment(\.colorScheme) private var cs
|
||||
|
||||
Reference in New Issue
Block a user