Add App Store screenshot harness + listing metadata
DEBUG-only screenshot support (seeded sample data via ScreenshotSeed, per-screen launch args, screenshot roots for both apps) so iPhone + Apple Watch App Store shots can be captured deterministically from the simulator — all excluded from release builds. Also seed ExerciseView's set-grid progress in init so it renders correctly on the first frame. Adds Scripts/metadata/ as the listing source of truth (copy, URLs, review notes, and the captured screenshots). Claude-Session: https://claude.ai/code/session_018gg69MaUetDNzWzBXisfMV
This commit is contained in:
@@ -4,6 +4,12 @@ All notable changes to this project are documented here.
|
|||||||
|
|
||||||
## June 2026
|
## June 2026
|
||||||
|
|
||||||
|
- Exercise detail now renders the set-progress grid correctly on the first frame
|
||||||
|
(seeded from the log in `init`) instead of filling in a frame later.
|
||||||
|
- Added a DEBUG-only screenshot harness (seeded sample data + `--screenshot
|
||||||
|
--screen <name>` launch args, excluded from release builds) for generating App
|
||||||
|
Store screenshots from the iPhone and Apple Watch simulators, plus the
|
||||||
|
`Scripts/metadata/` App Store listing source of truth.
|
||||||
- Redesigned the Apple Watch app into a focused workout runner: it opens directly
|
- Redesigned the Apple Watch app into a focused workout runner: it opens directly
|
||||||
on the active workout's exercise list (or prompts you to start one on iPhone),
|
on the active workout's exercise list (or prompts you to start one on iPhone),
|
||||||
and each exercise runs as a horizontally-paged HIIT cycle — a count-up work
|
and each exercise runs as a horizontally-paged HIIT cycle — a count-up work
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"primary": "HEALTH_AND_FITNESS",
|
||||||
|
"secondary": null
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Workouts Plus
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
https://indie.rzen.dev/apps/workouts/privacy
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Splits you run from your wrist
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 185 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 704 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 232 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
Workouts Plus is a workout tracker for iPhone and Apple Watch. Plan your routine on the phone, then leave it in your pocket and run the whole session from your wrist.
|
||||||
|
|
||||||
|
PLAN YOUR SPLITS
|
||||||
|
- Organize exercises into reusable routines with their own colors and icons
|
||||||
|
- Start from a built-in catalog of bodyweight and machine exercises, or add your own
|
||||||
|
- Set default sets, reps and weight — or a timed duration — for each exercise
|
||||||
|
|
||||||
|
RUN IT FROM YOUR WRIST
|
||||||
|
- Start a workout on iPhone and your Apple Watch opens straight into it
|
||||||
|
- Each exercise runs as a HIIT-style cycle: a count-up work phase, then a rest that counts down with a haptic countdown before the next set slides in
|
||||||
|
- Tap One More for a bonus set, or Done to finish and move on
|
||||||
|
- Set your rest length once on iPhone and it syncs to the watch
|
||||||
|
|
||||||
|
PROGRESS THAT KEEPS UP
|
||||||
|
- Finish a set on the watch and the iPhone advances in real time — no need to touch your phone
|
||||||
|
- Per-exercise weight-progression charts show how you're trending across past sessions
|
||||||
|
|
||||||
|
YOUR DATA, IN YOUR ICLOUD
|
||||||
|
- Everything is stored as plain files in your own iCloud Drive — browsable in the Files app and synced across your devices through Apple, never through a server of mine
|
||||||
|
- No accounts, no ads, no tracking
|
||||||
|
- iCloud is required
|
||||||
|
|
||||||
|
Built for lifters who plan on the phone and train from the wrist.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
gym,strength,exercise,routine,reps,sets,lifting,training,fitness,watch,tracker,rest,hiit,interval
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
https://indie.rzen.dev/apps/workouts
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Plan your splits on iPhone, then run the whole session from your Apple Watch — count-up work, count-down rests, and set progress that syncs back live.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
https://indie.rzen.dev/apps/workouts/support
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"contactFirstName": "Rouslan",
|
||||||
|
"contactLastName": "Zenetl",
|
||||||
|
"contactPhone": "+16465840598",
|
||||||
|
"contactEmail": "rzenetl@gmail.com",
|
||||||
|
"demoAccountRequired": false,
|
||||||
|
"notes": "Workouts Plus stores its data as JSON files in the reviewer's own iCloud Drive, so please sign into iCloud with iCloud Drive enabled before testing — without iCloud the app shows a blocking \"iCloud Required\" screen. To try it: open the iPhone app, tap \"Add Starter Splits\" in Settings (or create a split), then start a workout from a split. Starting a workout launches the paired Apple Watch app into the session via HealthKit (allow the Health prompt), so please review on a paired Apple Watch. On the watch, tap an exercise and swipe through the work/rest pages; completing a set syncs back to the iPhone in real time. The HealthKit permission is used only to launch and run the on-watch workout session — the app does not read your health data."
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#if DEBUG
|
||||||
|
import Foundation
|
||||||
|
import SwiftData
|
||||||
|
|
||||||
|
/// DEBUG-only sample data + launch-arg plumbing for App Store screenshot capture.
|
||||||
|
/// Activated by launching with `--screenshot`; `--screen <name>` picks which screen
|
||||||
|
/// the app renders (see each target's screenshot root). Never compiled into release.
|
||||||
|
enum ScreenshotSeed {
|
||||||
|
static var isActive: Bool { CommandLine.arguments.contains("--screenshot") }
|
||||||
|
|
||||||
|
static func screen(default def: String) -> String {
|
||||||
|
let args = CommandLine.arguments
|
||||||
|
if let i = args.firstIndex(of: "--screen"), i + 1 < args.count { return args[i + 1] }
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a believable in-progress workout, a few finished sessions (so charts have
|
||||||
|
/// a trend), and the starter splits. Returns the in-progress workout to display.
|
||||||
|
@MainActor
|
||||||
|
@discardableResult
|
||||||
|
static func populate(_ context: ModelContext) -> Workout? {
|
||||||
|
// Idempotent: clear any prior seed so repeated launches don't stack duplicates
|
||||||
|
// (cascade deletes take the child exercises/logs with them).
|
||||||
|
try? context.delete(model: Workout.self)
|
||||||
|
try? context.delete(model: Split.self)
|
||||||
|
|
||||||
|
let cal = Calendar(identifier: .gregorian)
|
||||||
|
let today = Date()
|
||||||
|
func daysAgo(_ n: Int) -> Date { cal.date(byAdding: .day, value: -n, to: today) ?? today }
|
||||||
|
|
||||||
|
// ---- Splits (with exercises) ------------------------------------------
|
||||||
|
let splits: [(String, String, String, [(String, Int, Int, Int)])] = [
|
||||||
|
("Upper Body", "purple", "dumbbell.fill", [
|
||||||
|
("Bench Press", 4, 10, 135), ("Overhead Press", 4, 10, 75),
|
||||||
|
("Lat Pulldown", 4, 12, 120), ("Bicep Curl", 3, 12, 30),
|
||||||
|
]),
|
||||||
|
("Lower Body", "blue", "figure.strengthtraining.traditional", [
|
||||||
|
("Back Squat", 5, 5, 185), ("Romanian Deadlift", 4, 8, 155),
|
||||||
|
("Leg Press", 4, 12, 270), ("Calf Raise", 4, 15, 90),
|
||||||
|
]),
|
||||||
|
("Core", "orange", "figure.core.training", [
|
||||||
|
("Plank", 3, 0, 0), ("Hanging Leg Raise", 3, 12, 0),
|
||||||
|
("Cable Crunch", 3, 15, 50),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
|
||||||
|
for (sIndex, s) in splits.enumerated() {
|
||||||
|
let split = Split(id: ULID.make(), name: s.0, color: s.1, systemImage: s.2,
|
||||||
|
order: sIndex, createdAt: today, updatedAt: today, jsonRelativePath: "")
|
||||||
|
context.insert(split)
|
||||||
|
for (eIndex, e) in s.3.enumerated() {
|
||||||
|
let isDuration = e.0 == "Plank"
|
||||||
|
let ex = Exercise(id: ULID.make(), name: e.0, order: eIndex, sets: e.1, reps: e.2,
|
||||||
|
weight: e.3, loadType: (isDuration ? LoadType.duration : .weight).rawValue,
|
||||||
|
durationTotalSeconds: isDuration ? 45 : 0, weightLastUpdated: today,
|
||||||
|
weightReminderTimeIntervalWeeks: 2)
|
||||||
|
ex.split = split
|
||||||
|
context.insert(ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let upper = splits[0]
|
||||||
|
|
||||||
|
// ---- Past finished sessions (Bench Press trend for the chart) ----------
|
||||||
|
let benchTrend = [115, 120, 125, 130]
|
||||||
|
for (i, w) in benchTrend.enumerated() {
|
||||||
|
let date = daysAgo((benchTrend.count - i) * 4)
|
||||||
|
let workout = Workout(id: ULID.make(), splitID: nil, splitName: upper.0, start: date,
|
||||||
|
end: date.addingTimeInterval(2400), statusRaw: WorkoutStatus.completed.rawValue,
|
||||||
|
createdAt: date, updatedAt: date, jsonRelativePath: "")
|
||||||
|
context.insert(workout)
|
||||||
|
for (eIndex, e) in upper.3.enumerated() {
|
||||||
|
let weight = e.0 == "Bench Press" ? w : e.3
|
||||||
|
let log = WorkoutLog(id: ULID.make(), exerciseName: e.0, order: eIndex, sets: e.1,
|
||||||
|
reps: e.2, weight: weight, loadType: LoadType.weight.rawValue,
|
||||||
|
durationTotalSeconds: 0, currentStateIndex: e.1, completed: true,
|
||||||
|
statusRaw: WorkoutStatus.completed.rawValue, notes: nil, date: date)
|
||||||
|
log.workout = workout
|
||||||
|
context.insert(log)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- The current in-progress session -----------------------------------
|
||||||
|
let workout = Workout(id: ULID.make(), splitID: nil, splitName: upper.0, start: today,
|
||||||
|
end: nil, statusRaw: WorkoutStatus.inProgress.rawValue,
|
||||||
|
createdAt: today, updatedAt: today, jsonRelativePath: "")
|
||||||
|
context.insert(workout)
|
||||||
|
// Bench Press done, Overhead Press partway, the rest to go.
|
||||||
|
let progress = [(4, WorkoutStatus.completed), (2, .inProgress), (0, .notStarted), (0, .notStarted)]
|
||||||
|
for (eIndex, e) in upper.3.enumerated() {
|
||||||
|
let (idx, status) = progress[eIndex]
|
||||||
|
let log = WorkoutLog(id: ULID.make(), exerciseName: e.0, order: eIndex, sets: e.1, reps: e.2,
|
||||||
|
weight: e.0 == "Bench Press" ? 135 : e.3, loadType: LoadType.weight.rawValue,
|
||||||
|
durationTotalSeconds: 0, currentStateIndex: idx, completed: status == .completed,
|
||||||
|
statusRaw: status.rawValue, notes: nil, date: today)
|
||||||
|
log.workout = workout
|
||||||
|
context.insert(log)
|
||||||
|
}
|
||||||
|
|
||||||
|
try? context.save()
|
||||||
|
return workout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -35,17 +35,22 @@ struct ExerciseProgressView: View {
|
|||||||
@State private var showingCancelConfirm = false
|
@State private var showingCancelConfirm = false
|
||||||
@State private var didRestorePage = false
|
@State private var didRestorePage = false
|
||||||
|
|
||||||
init(doc: Binding<WorkoutDocument>, logID: String, onChange: @escaping () -> Void) {
|
/// Forces the starting page (used only by the DEBUG screenshot host to land on a
|
||||||
|
/// rest page). Always nil in normal use.
|
||||||
|
private let debugInitialPage: Int?
|
||||||
|
|
||||||
|
init(doc: Binding<WorkoutDocument>, logID: String, onChange: @escaping () -> Void, debugInitialPage: Int? = nil) {
|
||||||
self._doc = doc
|
self._doc = doc
|
||||||
self.logID = logID
|
self.logID = logID
|
||||||
self.onChange = onChange
|
self.onChange = onChange
|
||||||
|
self.debugInitialPage = debugInitialPage
|
||||||
|
|
||||||
let log = doc.wrappedValue.logs.first { $0.id == logID }
|
let log = doc.wrappedValue.logs.first { $0.id == logID }
|
||||||
let sets = max(1, log?.sets ?? 1)
|
let sets = max(1, log?.sets ?? 1)
|
||||||
_setCount = State(initialValue: sets)
|
_setCount = State(initialValue: sets)
|
||||||
// Resume on the first unfinished set's work page (clamped to the last set).
|
// Resume on the first unfinished set's work page (clamped to the last set).
|
||||||
let completed = min(max(0, log?.currentStateIndex ?? 0), sets - 1)
|
let completed = min(max(0, log?.currentStateIndex ?? 0), sets - 1)
|
||||||
_currentPage = State(initialValue: completed * 2)
|
_currentPage = State(initialValue: debugInitialPage ?? (completed * 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
private var log: WorkoutLogDocument? {
|
private var log: WorkoutLogDocument? {
|
||||||
@@ -96,13 +101,16 @@ struct ExerciseProgressView: View {
|
|||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
// Jump to the first unfinished set. A paged TabView can settle on page 0 on
|
// Jump to the first unfinished set. A paged TabView can settle on page 0 on
|
||||||
// first layout, so re-assert once more after this run loop.
|
// first layout, so re-assert once more after this run loop. (The screenshot
|
||||||
|
// host pins an explicit page, so skip the resume jump there.)
|
||||||
guard !didRestorePage else { return }
|
guard !didRestorePage else { return }
|
||||||
didRestorePage = true
|
didRestorePage = true
|
||||||
|
if debugInitialPage == nil {
|
||||||
jumpToResumePage()
|
jumpToResumePage()
|
||||||
Task { @MainActor in jumpToResumePage() }
|
Task { @MainActor in jumpToResumePage() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Move to the resume page without animation, only if we're not already there
|
/// Move to the resume page without animation, only if we're not already there
|
||||||
/// (so a re-assert after a TabView snap-to-0 is a no-op in the common case).
|
/// (so a re-assert after a TabView snap-to-0 is a no-op in the common case).
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ final class WatchAppServices {
|
|||||||
let container = WorkoutsModelContainer.make()
|
let container = WorkoutsModelContainer.make()
|
||||||
self.container = container
|
self.container = container
|
||||||
self.bridge = WatchConnectivityBridge(container: container)
|
self.bridge = WatchConnectivityBridge(container: container)
|
||||||
|
#if DEBUG
|
||||||
|
if ScreenshotSeed.isActive { ScreenshotSeed.populate(container.mainContext) }
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func activate() {
|
func activate() {
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#if DEBUG
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftData
|
||||||
|
|
||||||
|
/// DEBUG-only root for App Store screenshot capture on the watch. Renders one
|
||||||
|
/// fully-formed screen (chosen by `--screen`) against the seeded cache so captures are
|
||||||
|
/// deterministic. Never compiled into release.
|
||||||
|
struct WatchScreenshotRoot: View {
|
||||||
|
let services: WatchAppServices
|
||||||
|
|
||||||
|
private var activeWorkout: Workout? {
|
||||||
|
let context = services.container.mainContext
|
||||||
|
var descriptor = FetchDescriptor<Workout>(sortBy: [SortDescriptor(\.start, order: .reverse)])
|
||||||
|
descriptor.fetchLimit = 25
|
||||||
|
let workouts = (try? context.fetch(descriptor)) ?? []
|
||||||
|
return workouts.first { $0.status == .inProgress } ?? workouts.first
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
content
|
||||||
|
.environment(services.bridge)
|
||||||
|
.modelContainer(services.container)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var content: some View {
|
||||||
|
if let workout = activeWorkout {
|
||||||
|
switch ScreenshotSeed.screen(default: "list") {
|
||||||
|
case "work":
|
||||||
|
ProgressHost(workout: workout, exerciseName: "Lat Pulldown", page: nil)
|
||||||
|
case "rest":
|
||||||
|
ProgressHost(workout: workout, exerciseName: "Lat Pulldown", page: 1)
|
||||||
|
default:
|
||||||
|
NavigationStack { WorkoutLogListView(workout: workout) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Color.black
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hosts the progress view with a working-copy binding (and an optional pinned page so
|
||||||
|
/// we can capture a rest phase, which the normal resume logic never lands on).
|
||||||
|
private struct ProgressHost: View {
|
||||||
|
@State private var doc: WorkoutDocument
|
||||||
|
private let logID: String
|
||||||
|
private let page: Int?
|
||||||
|
|
||||||
|
init(workout: Workout, exerciseName: String, page: Int?) {
|
||||||
|
let document = WorkoutDocument(from: workout)
|
||||||
|
_doc = State(initialValue: document)
|
||||||
|
logID = document.logs.first { $0.exerciseName == exerciseName }?.id ?? document.logs.first?.id ?? ""
|
||||||
|
self.page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ExerciseProgressView(doc: $doc, logID: logID, onChange: {}, debugInitialPage: page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -15,6 +15,19 @@ struct WorkoutsWatchApp: App {
|
|||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
|
#if DEBUG
|
||||||
|
if ScreenshotSeed.isActive {
|
||||||
|
WatchScreenshotRoot(services: services)
|
||||||
|
} else {
|
||||||
|
root
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
root
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var root: some View {
|
||||||
ContentView()
|
ContentView()
|
||||||
.environment(services.bridge)
|
.environment(services.bridge)
|
||||||
.environment(appDelegate.sessionManager)
|
.environment(appDelegate.sessionManager)
|
||||||
@@ -22,4 +35,3 @@ struct WorkoutsWatchApp: App {
|
|||||||
.task { services.activate() }
|
.task { services.activate() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ final class AppServices {
|
|||||||
self.container = container
|
self.container = container
|
||||||
self.syncEngine = SyncEngine(container: container)
|
self.syncEngine = SyncEngine(container: container)
|
||||||
self.watchBridge = PhoneConnectivityBridge(container: container, syncEngine: syncEngine)
|
self.watchBridge = PhoneConnectivityBridge(container: container, syncEngine: syncEngine)
|
||||||
|
#if DEBUG
|
||||||
|
if ScreenshotSeed.isActive { ScreenshotSeed.populate(container.mainContext) }
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Launch step: resolve iCloud and reconcile the cache. Idempotent — repeated
|
/// Launch step: resolve iCloud and reconcile the cache. Idempotent — repeated
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#if DEBUG
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftData
|
||||||
|
|
||||||
|
/// DEBUG-only root used for App Store screenshot capture. Bypasses the iCloud gate and
|
||||||
|
/// renders one fully-formed screen (chosen by `--screen`) against the seeded cache, so
|
||||||
|
/// captures are deterministic with no UI automation. Never compiled into release.
|
||||||
|
struct ScreenshotRootView: View {
|
||||||
|
let services: AppServices
|
||||||
|
|
||||||
|
private var activeWorkout: Workout? {
|
||||||
|
let context = services.container.mainContext
|
||||||
|
var descriptor = FetchDescriptor<Workout>(sortBy: [SortDescriptor(\.start, order: .reverse)])
|
||||||
|
descriptor.fetchLimit = 25
|
||||||
|
let workouts = (try? context.fetch(descriptor)) ?? []
|
||||||
|
return workouts.first { $0.status == .inProgress } ?? workouts.first
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
content
|
||||||
|
.environment(services)
|
||||||
|
.environment(services.syncEngine)
|
||||||
|
.modelContainer(services.container)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var content: some View {
|
||||||
|
if let workout = activeWorkout {
|
||||||
|
switch ScreenshotSeed.screen(default: "workouts") {
|
||||||
|
case "exercise":
|
||||||
|
let logID = WorkoutDocument(from: workout).logs.first { $0.exerciseName == "Bench Press" }?.id
|
||||||
|
NavigationStack { ExerciseView(workout: workout, logID: logID ?? "") }
|
||||||
|
case "settings":
|
||||||
|
SettingsView()
|
||||||
|
default:
|
||||||
|
NavigationStack { WorkoutLogListView(workout: workout) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Color(.systemBackground)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -35,7 +35,11 @@ struct ExerciseView: View {
|
|||||||
init(workout: Workout, logID: String, seedDoc: WorkoutDocument? = nil) {
|
init(workout: Workout, logID: String, seedDoc: WorkoutDocument? = nil) {
|
||||||
self.workout = workout
|
self.workout = workout
|
||||||
self.logID = logID
|
self.logID = logID
|
||||||
_doc = State(initialValue: seedDoc ?? WorkoutDocument(from: workout))
|
let initialDoc = seedDoc ?? WorkoutDocument(from: workout)
|
||||||
|
_doc = State(initialValue: initialDoc)
|
||||||
|
// Seed progress from the log so the set grid is correct on the first frame
|
||||||
|
// (onAppear also refreshes it, but that lags the initial render).
|
||||||
|
_progress = State(initialValue: initialDoc.logs.first { $0.id == logID }?.currentStateIndex ?? 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The log being edited within the working doc.
|
/// The log being edited within the working doc.
|
||||||
|
|||||||
@@ -14,6 +14,19 @@ struct WorkoutsApp: App {
|
|||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
|
#if DEBUG
|
||||||
|
if ScreenshotSeed.isActive {
|
||||||
|
ScreenshotRootView(services: services)
|
||||||
|
} else {
|
||||||
|
root
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
root
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var root: some View {
|
||||||
RootGateView()
|
RootGateView()
|
||||||
.environment(services)
|
.environment(services)
|
||||||
.environment(services.syncEngine)
|
.environment(services.syncEngine)
|
||||||
@@ -21,4 +34,3 @@ struct WorkoutsApp: App {
|
|||||||
.task { await services.bootstrap() }
|
.task { await services.bootstrap() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user