OmniPose Fit is a real-time, computer-vision-powered fitness app that tracks workout repetitions, corrects posture dynamically based on geometric constraints, and gamifies calisthenics skill progressions — wrapped in a premium dark-mode experience with vibrant accent feedback.
Built with Kotlin, Jetpack Compose, CameraX and ML Kit Pose Detection.
UI preview — rendered from the app's real design system (palette, layouts and component behaviour). Not a device screen recording.
Prebuilt APKs are published on the repo's
Releases page (produced by the
Build & Release APK GitHub Action).
- Open the latest release and download
OmniPoseFit-arm64-v8a.apk(for virtually all modern phones) — orOmniPoseFit-universal.apkif unsure of your device architecture. - On your phone, allow Install unknown apps for your browser/file manager.
- Tap the downloaded APK to install, then grant the camera permission on first launch.
Maintainers: push a tag like
v1.0.0(or run the workflow manually from the Actions tab) to build the APKs and attach them to a new GitHub Release.
- Dynamic Movement Parser — no hardcoded routines. The tracking engine
consumes JSON schemas (
assets/exercises/*.json) that declare which joint angles matter and the angle windows defining theSTART,INFLECTION_POINTandENDstates. Drop in a new JSON file to teach the app a new exercise. - Spatial Camera Guidance —
PoseValidatormeasures the lateral compression of matched left/right joints (torso-normalized) to detect whether you're capturing the movement's optimal plane. A friendly banner asks you to "turn 90° to capture your side profile" when a sagittal exercise is filmed front-on, then locks into a confirmation glow once the angle is optimal. - Anatomical Muscle Overlay — targeted muscle groups glow directly on a stylized front/back skeletal model (tap any region to inspect it) instead of cluttered text lists.
- Gamified Skill Tree — a pannable DAG progression map (Push-Up → Diamond Push-Up → Frog Stand → Handstand / Front Lever, plus pull, lever and leg branches) with three node states: locked (muted padlock), available (pulsing ring) and mastered (gold glow). Tapping a node opens a preview modal with a looping technique-demo video, anatomy highlights and prerequisites. Hitting a skill's rep goal in AI training fires a mastery celebration and unlocks the next nodes.
- Live Training HUD — schema-driven skeleton overlay with a live joint angle arc, an animated rep dial whose ring fills with movement depth, a tempo pulse pacer (visual ripple + optional audio pip), a state-machine ribbon (Start → Descent → Bottom → Ascent) and voice rep announcements.
com.grloepr.pushtrack
├── engine/ Pure-Kotlin domain core (KMP-migration ready)
│ ├── PoseSnapshot SDK-agnostic pose frame (ML Kit adapter at the edge)
│ ├── ExerciseSchema Parsed movement definition + progress normalisation
│ ├── SchemaParser JSON → schema, ExerciseLibrary asset loader
│ ├── DynamicExerciseEngine Generic rep state machine (schema-driven)
│ ├── PoseValidator Camera-plane validation (sagittal/frontal)
│ ├── AlignmentMonitor Hysteresis + smoothing for flicker-free guidance
│ └── TempoTracker Rep cadence stats
├── anatomy/ MuscleGroup taxonomy + interactive AnatomyCanvas
├── progression/ Skill DAG (CalisthenicsSkillGraph) + persisted SkillTreeState
├── analysis/ camera/ pose/ CameraX + ML Kit plumbing (Android-specific)
├── audio/ tts/ Tempo tick player, voice announcements
└── ui/
├── theme/ OmniPose dark palette (electric cyan / neon violet / volt lime / gold)
├── overlay/ PoseCoordinateMapper, SchemaTrackingOverlay, ScannerViewfinder
├── components/ RepCounterDial, TempoPulseIndicator, CameraAngleBanner,
│ StateMachineRibbon, VideoPlaceholder, MasteryCelebration, GlassPanel
├── tree/ SkillTreeScreen (DAG map) + SkillDetailSheet
└── screen/ OmniPoseApp root + TrainingScreen
{
"exercise_id": "squat",
"display_name": "Deep Squat",
"target_muscles": ["quadriceps", "gluteus_maximus", "hamstrings"],
"tracking_joints": {
"primary_angle": ["LEFT_HIP", "LEFT_KNEE", "LEFT_ANKLE"],
"secondary_angle": ["LEFT_SHOULDER", "LEFT_HIP", "LEFT_KNEE"]
},
"states": {
"START": { "primary_angle": { "min": 160, "max": 180 } },
"INFLECTION_POINT": { "primary_angle": { "less_than": 90 } },
"END": { "primary_angle": { "min": 160, "max": 180 } }
},
"validation": {
"optimal_camera_plane": "SAGITTAL",
"required_joints_visible": ["LEFT_HIP", "LEFT_KNEE", "LEFT_ANKLE", "RIGHT_KNEE"]
},
"tempo": { "pulse_interval_ms": 3000 },
"mastery_reps": 10
}Constraints support min, max, less_than, greater_than (AND-combined).
The engine walks SEARCHING → READY → ECCENTRIC → BOTTOM → CONCENTRIC and
counts a rep when the athlete returns to the END window after touching the
inflection point; turnarounds before full depth are surfaced as partial reps.
Isometric skills (dead hang, handstands, levers, frog stand) add a hold
block and describe the hold posture in INFLECTION_POINT:
"hold": { "target_ms": 20000 }The engine then runs a reduced SEARCHING → READY → BOTTOM (holding) machine:
keeping the posture for target_ms scores one rep, breaking it early after a
real attempt scores a partial, and the HUD dial counts hold seconds instead
of reps.
The skill detail sheet plays a looping, muted demo from
app/src/main/assets/previews/{skillNodeId}.mp4, falling back to
{exerciseSchemaId}.mp4, then to an animated placeholder.
Demo clips are plain assets — generate them however you like (Veo/Gemini, Runway, a self-hosted model, or real footage), then drop the MP4 into that folder and rebuild. No code change: the player auto-detects any file it finds.
Naming: files are matched to skill ids from progression/SkillGraph.kt
(deep_squat.mp4, pullup.mp4, handstand.mp4, front_lever.mp4, …).
Keep clips short, 16:9, and loopable. Normalise for a small APK with:
ffmpeg -i in.mp4 -vf scale=640:-2 -an -c:v libx264 -pix_fmt yuv420p \
-movflags +faststart app/src/main/assets/previews/deep_squat.mp4The app's differentiator — real-time camera + on-device pose estimation — is platform-native on every framework (Flutter/React Native wrap the same native SDKs, mobile-only). The chosen path is Kotlin Multiplatform:
- The
engine/,progression/and anatomy-model layers are already pure Kotlin operating on the SDK-agnosticPoseSnapshotabstraction — they can move into a KMP:sharedmodule unchanged. - Compose Multiplatform can carry the UI to iOS/desktop, while
camera/+pose/stay as thin per-platform bindings (CameraX + ML Kit on Android; AVFoundation + Vision/MediaPipe on iOS).
Requires JDK 11+ and an Android SDK with platform 36.
./gradlew assembleDebugCamera permission is required at runtime; everything runs on-device — no network needed.
