Add spoken exercise instructions (on-device TTS)
Speak exercise setup and form cues aloud with AVSpeechSynthesizer: - Library detail: a speaker toolbar button reads the full reference aloud. - Active workout: an opt-in "Speak Exercise Cues" setting speaks a brief cue (Setup/Execution/Cues) when an exercise starts, hands-free. - Settings › Voice: pick the voice (auto-prefers an installed enhanced/ premium English voice), a premium-download nudge shown only while on a basic voice, and a Speed/Pitch/Volume sheet with Reset to Defaults. On-device and offline; ducks other audio rather than stopping it. iPhone only for now. Shared SpeechSettings is the single source of truth for the voice/prosody, read fresh per utterance so changes preview live. Claude-Session: https://claude.ai/code/session_01BQcEWmAPA78338QuEwRkAh
This commit is contained in:
@@ -32,6 +32,29 @@ struct ExerciseInfoTests {
|
||||
}
|
||||
}
|
||||
|
||||
/// The spoken script (fed to `AVSpeechSynthesizer`) leads with the exercise name and
|
||||
/// its summary, and `brief` drops the reference-only sections so a hands-free workout
|
||||
/// cue reads Setup/Execution/Cues but not Common Mistakes/Progression.
|
||||
@Test func spokenScriptBriefDropsReferenceSections() throws {
|
||||
let info = try #require(ExerciseInfoLibrary.info(for: "Chest Press"))
|
||||
|
||||
let full = info.spokenScript(name: "Chest Press", brief: false)
|
||||
#expect(full.hasPrefix("Chest Press."))
|
||||
#expect(full.contains("Setup."))
|
||||
#expect(full.contains("Execution."))
|
||||
#expect(full.contains("Cues."))
|
||||
#expect(full.contains("Common Mistakes."))
|
||||
#expect(full.contains("Progression."))
|
||||
|
||||
let brief = info.spokenScript(name: "Chest Press", brief: true)
|
||||
#expect(brief.hasPrefix("Chest Press."))
|
||||
#expect(brief.contains("Setup."))
|
||||
#expect(brief.contains("Execution."))
|
||||
#expect(brief.contains("Cues."))
|
||||
#expect(!brief.contains("Common Mistakes."))
|
||||
#expect(!brief.contains("Progression."))
|
||||
}
|
||||
|
||||
/// Every bundled motion rig ships a parseable info page — the exporter copies
|
||||
/// `info.md` per entry, and the detail screen counts on the shape.
|
||||
@Test func everyBundledExerciseHasParseableInfo() throws {
|
||||
|
||||
Reference in New Issue
Block a user