Files
workouts/Workouts/Views/Settings/SettingsView.swift
T
rzen d5915a9552 Add HIIT watch runner, rest-time setting, and HealthKit watch auto-launch
- Redesign the watch app into an active-workout runner: a root gate shows the
  in-progress workout's exercises or prompts to start one on iPhone, and each
  exercise runs as a horizontally-paged HIIT cycle (count-up work, count-down
  rest with final-three-second haptics + auto-advance, One More / Done on the
  last set). Replaces the old history list.
- Add a configurable rest-between-sets duration in iPhone Settings (default 45s),
  synced to the watch over WatchConnectivity.
- Launch the watch app into the session when a workout starts on the phone via
  HealthKit (startWatchApp); the watch runs an HKWorkoutSession for foreground
  runtime and ends it when the workout finishes. Adds the HealthKit entitlement +
  Health usage strings on both targets and WKBackgroundModes on the watch.

Claude-Session: https://claude.ai/code/session_018gg69MaUetDNzWzBXisfMV
2026-06-19 16:16:44 -04:00

114 lines
4.3 KiB
Swift

//
// SettingsView.swift
// Workouts
//
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
//
import SwiftUI
import SwiftData
import IndieAbout
struct SettingsView: View {
@Environment(SyncEngine.self) private var sync
@Environment(\.modelContext) private var modelContext
@Environment(AppServices.self) private var services
@Query(sort: \Split.order) private var splits: [Split]
@AppStorage("restSeconds") private var restSeconds: Int = 45
@State private var showingAddSplitSheet = false
var body: some View {
NavigationStack {
Form {
// MARK: - Workout Section
Section(header: Text("Workout")) {
Stepper(value: $restSeconds, in: 10...180, step: 5) {
HStack {
Text("Rest Between Sets")
Spacer()
Text("\(restSeconds)s").foregroundColor(.secondary)
}
}
}
// MARK: - Splits Section
Section(header: Text("Splits")) {
if splits.isEmpty {
HStack {
Spacer()
VStack(spacing: 8) {
Image(systemName: "dumbbell.fill")
.font(.largeTitle)
.foregroundColor(.secondary)
Text("No Splits Yet")
.font(.headline)
.foregroundColor(.secondary)
Text("Create a split to organize your workout routine.")
.font(.caption)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
}
.padding(.vertical)
Spacer()
}
} else {
ForEach(splits) { split in
NavigationLink {
SplitDetailView(split: split)
} label: {
HStack {
Image(systemName: split.systemImage)
.foregroundColor(Color.color(from: split.color))
.frame(width: 24)
Text(split.name)
Spacer()
Text("\(split.exercisesArray.count)")
.foregroundColor(.secondary)
}
}
}
}
Button {
showingAddSplitSheet = true
} label: {
HStack {
Image(systemName: "plus.circle.fill")
.foregroundColor(.accentColor)
Text("Add Split")
}
}
Button {
Task { await SplitSeeder.seedDefaults(into: modelContext, using: sync) }
} label: {
HStack {
Image(systemName: "wand.and.sparkles")
.foregroundColor(.accentColor)
Text("Add Starter Splits")
}
}
}
// MARK: - About Section
Section {
IndieAbout(configuration: AppInfoConfiguration(
documents: [
.custom(title: "Changelog", filename: "CHANGELOG", extension: "md"),
.license(),
.acknowledgements()
]
))
}
}
.navigationTitle("Settings")
.sheet(isPresented: $showingAddSplitSheet) {
SplitAddEditView(split: nil)
}
.onChange(of: restSeconds) { _, _ in services.watchBridge.pushAll() }
}
}
}