LowerBackStretching

Lower Back & Legs Stretching

Native Android (Kotlin / Jetpack Compose) + Wear OS companion + iOS (Swift / SwiftUI) stretching app focused on lower back, legs, hips, and hamstrings.

What it does

100% local persistence by default; cloud sync only kicks in if you sign in.

Repo layout

content/        Shared JSON (stretches, programs, glossary) consumed by both apps
android/        Gradle project — three modules: :core (pure JVM), :app, :wear
ios/            iOS sources — LowerBackStretching/ (phone) + WatchApp/ (watch)
firebase/       Firestore rules + cloud-functions stubs (backend, optional)
.github/        Actions workflows (unit tests on every push, emulator nightly)

The same stretches.json / programs.json / glossary.json files live in android/app/src/main/assets/ and ios/LowerBackStretching/Resources/ — keep them in sync (or symlink) when editing content.

Android modules

Quick start

Android

Open android/ in Android Studio (Hedgehog or newer). Sync Gradle, plug in a device or start an emulator, and Run. Min SDK 26, Target SDK 36.

./gradlew :app:installDebug              # phone
./gradlew :wear:installDebug             # watch
./gradlew :core:test :app:testDebugUnitTest :wear:testDebugUnitTest

iOS

Open Xcode 15+, create a new iOS App named LowerBackStretching, drag in ios/LowerBackStretching/. Add a Watch App target named WatchApp, drag in ios/WatchApp/. iOS 17 deployment target. See ios/README.md for the full step-by-step.

Content / animations

Stretch entries carry an animation field — a set of normalized joint keyframes interpolated at runtime by StretchAnimationView (2D) and StretchAnimation3DView (3D). All 26 stretches have one. Keyframe data is generated by scripts/inject_animations.py; see content/README.md.

The legacy youtubeId field is still present in the JSON but is no longer used for playback (video embeds were removed — see “Health Connect & platform notes”). 12 of 26 entries have it empty.

CI

What’s done

The brainstorm plan shipped Waves 1–10:

# Wave Status
1 Foundation polish (dark mode, units, haptics, lifecycle save-state)
2 Audio (music + ambient + chime + ducking)
3 Routine management (reorder, duplicate, soft-delete, share-via-link)
4 Gamification (streaks, goals, XP, levels, achievements)
5 Health integration (Health Connect session write)
6 Education (glossary, “why this stretch”, what-you-should-feel)
7 Anatomy & body diagram (tap silhouette → routine)
8 Backend & social (sync interface; Firebase impl is opt-in)
9 Watch companion (Wear OS app sharing PlayerEngine)
10 Polish (PiP, share QR, system-calendar integration)

Health Connect & platform notes

Android is write-only by design. The app declares exactly one Health Connect permission — WRITE_EXERCISE — and reads nothing. An earlier release also requested READ_STEPS to power a “you walked 5k today, try a cooldown stretch” card on Home. That was removed in 1.0.8 (versionCode 11) rather than justified against Google Play’s Minimum Scope policy for Health Connect permissions, which requires each requested data type to be essential to a core, user-facing feature. The cooldown card was a nice-to- have, so deleting it was cheaper than defending it.

PRIVACY.md is the policy Google Play links to and reviewers read against the manifest — if you ever change the declared permissions, update that file in the same commit.

iOS mirrors this: the HealthKit step read and its cooldown card were removed for parity, and NSHealthShareUsageDescription dropped from Info.plist. HealthKit has no Minimum Scope equivalent, so this was a consistency choice rather than a compliance requirement — recorded here so the removal doesn’t later read as an accidental regression.

What’s next