Files
workouts/Workouts/Views/Routines/RoutineListView.swift
T
rzen 6e440317c4 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
2026-07-11 07:53:01 -04:00

119 lines
3.9 KiB
Swift

//
// RoutineListView.swift
// Workouts
//
// Created by rzen on 7/25/25 at 6:24 PM.
//
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
//
import SwiftUI
import SwiftData
/// The Routines library screen, pushed from Settings → Library: a plain list of
/// routines, one row each. Tapping a row opens `RoutineDetailView`; long-pressing
/// offers Delete (the sole routine-delete affordance); the toolbar's + adds a new
/// routine. Starter routines are seeded automatically on the true first run
/// (`SyncEngine.autoSeedIfEmpty`), so an empty library just invites creating one.
struct RoutineListView: View {
@Environment(SyncEngine.self) private var sync
@Query(sort: \Routine.name) private var routines: [Routine]
@State private var showingAddSheet = false
@State private var routineToDelete: Routine?
var body: some View {
List {
ForEach(routines) { routine in
NavigationLink {
RoutineDetailView(routine: routine)
} label: {
RoutineRow(routine: routine)
}
.contextMenu {
Button(role: .destructive) {
routineToDelete = routine
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}
.listStyle(.insetGrouped)
.overlay {
if routines.isEmpty {
ContentUnavailableView(
"No Routines Yet",
systemImage: "dumbbell.fill",
description: Text("Create a routine to organize your workout routine.")
)
}
}
.navigationTitle("Routines")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
showingAddSheet = true
} label: {
Image(systemName: "plus")
}
.accessibilityLabel("Add Routine")
}
}
.sheet(isPresented: $showingAddSheet) {
RoutineAddEditView(routine: nil)
}
.confirmationDialog(
"Delete Routine?",
isPresented: Binding(
get: { routineToDelete != nil },
set: { if !$0 { routineToDelete = nil } }
),
titleVisibility: .visible,
presenting: routineToDelete
) { routine in
Button("Delete", role: .destructive) {
Task { await sync.delete(routine: routine) }
routineToDelete = nil
}
Button("Cancel", role: .cancel) {
routineToDelete = nil
}
} message: { routine in
Text("This will permanently delete \"\(routine.name)\" and all its exercises. Past workouts are kept.")
}
}
}
/// A single routine row: a small rounded-square badge (the routine's color at low
/// opacity, its symbol tinted full-strength) beside the routine name and its
/// exercise count.
private struct RoutineRow: View {
var routine: Routine
var body: some View {
HStack(spacing: 12) {
Image(systemName: routine.systemImage)
.font(.title3)
.foregroundStyle(routineColor)
.frame(width: 36, height: 36)
.background(routineColor.opacity(0.12), in: RoundedRectangle(cornerRadius: 8))
VStack(alignment: .leading, spacing: 2) {
Text(routine.name)
.foregroundStyle(.primary)
Text("\(routine.exercisesArray.count) exercises")
.font(.caption)
.foregroundStyle(.secondary)
}
}
.padding(.vertical, 2)
}
private var routineColor: Color {
Color.color(from: routine.color)
}
}