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,118 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user