Restructure into a three-tab app with Progress, goals, and Meditation
The UX redesign's first landing (spec in UX-REDESIGN.md): ContentView becomes a Today / Progress / Settings TabView, "Routine" replaces "Split" in every user-facing string and view name (code-level types keep their names), and workout starting moves to shared WorkoutStarter / StartedWorkoutNavigator plumbing. - New Progress tab: weekly goal streaks, workout trends, per-exercise weight progression, achievements, and the full history list (WorkoutLogsView -> WorkoutHistoryView). - Goals: stable categories workouts roll up to, managed from Settings. - New Meditation exercise + starter routine; timed sits record to Apple Health as Mind & Body sessions. Claude-Session: https://claude.ai/code/session_012qw2itfzKyEJ1HpsFt8Ex4
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// WorkoutHistoryView.swift
|
||||
// Workouts
|
||||
//
|
||||
// Created by rzen on 7/13/25 at 6:52 PM.
|
||||
//
|
||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
/// The full workout history list. Formerly the "Log" tab's root (`WorkoutLogsView`);
|
||||
/// now pushed from the Progress tab, which absorbed history in the four-tab redesign.
|
||||
struct WorkoutHistoryView: View {
|
||||
@Environment(SyncEngine.self) private var sync
|
||||
@Environment(AppServices.self) private var services
|
||||
|
||||
@Query(sort: \Workout.start, order: .reverse)
|
||||
private var workouts: [Workout]
|
||||
|
||||
@State private var itemToDelete: Workout?
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(workouts) { workout in
|
||||
NavigationLink {
|
||||
WorkoutLogListView(workout: workout)
|
||||
} label: {
|
||||
CalendarListItem(
|
||||
date: workout.start,
|
||||
title: workout.routineName ?? Routine.unnamed,
|
||||
subtitle: getSubtitle(for: workout),
|
||||
subtitle2: workout.statusName
|
||||
)
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
Button {
|
||||
itemToDelete = workout
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
.overlay {
|
||||
if workouts.isEmpty {
|
||||
ContentUnavailableView(
|
||||
"No Workouts Yet",
|
||||
systemImage: "list.bullet.clipboard",
|
||||
description: Text("Start a new workout from one of your routines.")
|
||||
)
|
||||
}
|
||||
}
|
||||
.navigationTitle("History")
|
||||
// Write-queue banner tiers (calm "pending" capsule / prominent fault).
|
||||
// On the log screen so a stalled save is visible where editing happens.
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
SyncStatusBanner()
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Delete Workout?",
|
||||
isPresented: Binding(
|
||||
get: { itemToDelete != nil },
|
||||
set: { if !$0 { itemToDelete = nil } }
|
||||
),
|
||||
titleVisibility: .visible,
|
||||
presenting: itemToDelete
|
||||
) { workout in
|
||||
Button("Delete", role: .destructive) {
|
||||
Task { await sync.delete(workout: workout) }
|
||||
itemToDelete = nil
|
||||
}
|
||||
// Only offered when the workout actually has a Health record. Capture
|
||||
// the UUID before the delete prunes the cache entity.
|
||||
if let healthUUID = workout.metricHealthKitWorkoutUUID {
|
||||
Button("Delete + Remove from Apple Health", role: .destructive) {
|
||||
services.workoutHealthDeleter.deleteFromHealth(uuidString: healthUUID)
|
||||
Task { await sync.delete(workout: workout) }
|
||||
itemToDelete = nil
|
||||
}
|
||||
}
|
||||
Button("Cancel", role: .cancel) {
|
||||
itemToDelete = nil
|
||||
}
|
||||
} message: { workout in
|
||||
if workout.metricSourceRaw == MetricSource.watch.rawValue {
|
||||
Text("This workout was recorded by Apple Watch; if it can't be removed from Apple Health here, delete it in the Health app.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func getSubtitle(for workout: Workout) -> String {
|
||||
if workout.status == .completed, let endDate = workout.end {
|
||||
return workout.start.humanTimeInterval(to: endDate)
|
||||
} else {
|
||||
return workout.start.formattedDate()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user