Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7f8371d
feat: register muscle artwork license
Devasy Oct 8, 2026
110ba3c
feat: ask optional gender during onboarding
Devasy Oct 8, 2026
3018c58
feat: show detailed selectable muscle anatomy
Devasy Oct 8, 2026
763a2c8
feat: render detailed muscle figures and intensity palettes
Devasy Oct 8, 2026
baacae9
feat: show exercise muscle movement phases
Devasy Oct 8, 2026
1246529
feat: highlight selected muscle anatomy
Devasy Oct 8, 2026
d5a0fd9
feat: add optional gender and diagram preferences
Devasy Oct 8, 2026
139a5fe
feat: persist independent gender and body figure preferences
Devasy Oct 8, 2026
e66c354
feat: lock path parsing dependency
Devasy Oct 8, 2026
c66cc48
feat: bundle muscle artwork notices and path parser
Devasy Oct 8, 2026
93553c6
feat: attribute MIT-licensed MuscleMap geometry
Devasy Oct 8, 2026
f8c016c
feat: define body figure and optional gender preferences
Devasy Oct 8, 2026
3bac708
feat: add detailed front and back anatomical outlines
Devasy Oct 8, 2026
eb58fd0
feat: define standard exercise muscle motion phases
Devasy Oct 8, 2026
9deabe0
feat: show labelled blue and red exercise muscle phases
Devasy Oct 8, 2026
d83fca6
feat: add optional gender picker
Devasy Oct 8, 2026
733f017
feat: test detailed muscle rendering and selection
Devasy Oct 8, 2026
ed9b6fb
docs: add release notes for muscle anatomy and phase views
Devasy Oct 8, 2026
6c17a5a
test: cover gender preferences onboarding and exercise phase palettes
Devasy Oct 8, 2026
ce0b2c7
fix: keep gender selection controlled by saved preference
Devasy Oct 9, 2026
840f349
fix: match shoulder press phase targets to catalogue IDs
Devasy Oct 9, 2026
7f982eb
test: cover failed gender saves and shoulder press phase maps
Devasy Oct 9, 2026
493387a
Make saved gender the app-wide muscle diagram selection
Devasy Oct 9, 2026
62d5503
Make saved gender the app-wide muscle diagram selection
Devasy Oct 9, 2026
6e277cd
Make saved gender the app-wide muscle diagram selection
Devasy Oct 9, 2026
a8f0085
Prompt existing users once for gender before entering the app
Devasy Oct 9, 2026
687eb6a
Prompt existing users once for gender before entering the app
Devasy Oct 9, 2026
8d88cb2
Prompt existing users once for gender before entering the app
Devasy Oct 9, 2026
cd08b3a
Document and verify one-time gender setup and shared diagrams
Devasy Oct 9, 2026
d1a8e0f
Document and verify one-time gender setup and shared diagrams
Devasy Oct 9, 2026
49dd666
Document and verify one-time gender setup and shared diagrams
Devasy Oct 9, 2026
2b64317
Test upgrade gender prompt persistence restart and failed saves
Devasy Oct 9, 2026
815ad86
Enclose profile save error feedback in a mounted guard block
Devasy Oct 9, 2026
0e61cf7
Assert both shoulder press catalogue targets in phase regression test
Devasy Oct 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ User-facing changes. Follow [the changelog format](docs/changelog-format.md).

## [Unreleased]

## [2.1.7]

### Features added
- View detailed front and back muscle diagrams with selectable muscles and weekly volume shading.
- Existing users are asked for gender once after upgrading; the saved choice selects diagrams throughout the app and can subsequently be changed only in Profile.
- Explore labelled Stretched and Contracted exercise phases with blue and rose-red intensity scales for supported exercises.

### Known limitations
- Muscle phase views illustrate standard technique and catalogue target involvement; they do not measure live muscle stretch or force. Custom and unsupported exercises show target involvement only.

## [2.1.6]

### Features added
Expand Down
26 changes: 26 additions & 0 deletions workout-logger/THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Body diagram geometry

Muscle outlines are derived from MuscleMap by Melih Colpan (https://github.com/melihcolpan/MuscleMap), via openGym's body-paths.js at commit f049465c1e4732c84e7077286dc99a66fdd2ed23 (https://github.com/DuarteSantos8/openGym).
Only the MIT-licensed geometry is reused. The Flutter renderer and RepForge muscle mapping are implemented for RepForge.

MIT License

Copyright (c) 2026 Melih Colpan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
4 changes: 4 additions & 0 deletions workout-logger/lib/data/body_figure.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/// Diagram geometry selected from the saved gender; not used for health scores.
enum BodyFigure { male, female }

enum UserGender { male, female, preferNotToSay }
505 changes: 505 additions & 0 deletions workout-logger/lib/data/body_geometry.dart

Large diffs are not rendered by default.

160 changes: 160 additions & 0 deletions workout-logger/lib/data/exercise_muscle_motion.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import '../models/models.dart';

/// Illustrative prime movers for standard technique, excluding stabilizers.
/// Activation percentages come from the catalogue; they are not measurements
/// of muscle length or contraction force. Unknown/custom/isometric exercises
/// deliberately have no inferred motion phases.
class ExerciseMuscleMotion {
const ExerciseMuscleMotion(this.muscles, this.stretched, this.contracted);
final List<String> muscles;
final String stretched;
final String contracted;

static ExerciseMuscleMotion? forExercise(Exercise exercise) =>
exercise.isCustom ? null : _motions[exercise.id];
}

const _chestPress = ExerciseMuscleMotion(
['chest', 'upper_chest', 'triceps', 'front_delts'],
'Lower the weight with control toward your chest.',
'Press the weight away from your chest.',
);
const _chestFly = ExerciseMuscleMotion(
['chest'],
'Open your arms with control.',
'Bring your arms together.',
);
const _curl = ExerciseMuscleMotion(
['biceps'],
'Lower the weight as your elbows extend.',
'Curl the weight as your elbows bend.',
);
const _triceps = ExerciseMuscleMotion(
['triceps'],
'Allow your elbows to bend with control.',
'Extend your elbows against resistance.',
);
const _squat = ExerciseMuscleMotion(
['quads', 'glutes'],
'Lower into the squat as your knees and hips bend.',
'Stand up by extending your knees and hips.',
);
const _row = ExerciseMuscleMotion(
['back', 'lats', 'biceps', 'rear_delts'],
'Extend your arms and allow your shoulder blades to move forward with control.',
'Pull toward your torso, drawing your shoulder blades back.',
);
const _pulldown = ExerciseMuscleMotion(
['lats', 'biceps'],
'Return toward the overhead position with control.',
'Pull down by bending your elbows and bringing your upper arms toward your torso.',
);
const _shoulderPress = ExerciseMuscleMotion(
['shoulders', 'triceps'],
'Lower the weight toward shoulder height with control.',
'Press the weight overhead.',
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const _motions = <String, ExerciseMuscleMotion>{
'bench_press': _chestPress,
'incline_bench_press': _chestPress,
'dumbbell_bench_press': _chestPress,
'incline_dumbbell_press': _chestPress,
'close_grip_bench': _chestPress,
'cable_fly': _chestFly,
'pec_deck': _chestFly,
'push_ups': ExerciseMuscleMotion(
['chest', 'triceps', 'front_delts'],
'Lower your body toward the floor with control.',
'Push your body away from the floor.',
),
'dips': ExerciseMuscleMotion(
['chest', 'triceps', 'front_delts'],
'Lower your body as your elbows bend.',
'Press up by extending your elbows.',
),
'lat_pulldown': _pulldown,
'pull_ups': ExerciseMuscleMotion(
['lats', 'biceps'],
'Lower your body toward a hang with control.',
'Pull your body upward.',
),
'chin_ups': ExerciseMuscleMotion(
['lats', 'biceps'],
'Lower your body toward a hang with control.',
'Pull your body upward.',
),
'barbell_row': _row,
'dumbbell_row': _row,
'seated_cable_row': _row,
't_bar_row': _row,
'overhead_press': _shoulderPress,
'dumbbell_shoulder_press': _shoulderPress,
'lateral_raise': ExerciseMuscleMotion(
['side_delts'],
'Lower your arms toward your sides.',
'Raise your arms out to your sides.',
),
'front_raise': ExerciseMuscleMotion(
['front_delts'],
'Lower your arms with control.',
'Raise your arms in front of you.',
),
'rear_delt_fly': ExerciseMuscleMotion(
['rear_delts'],
'Bring your arms forward with control.',
'Open your arms out to the sides.',
),
'shrugs': ExerciseMuscleMotion(
['traps'],
'Lower your shoulders with control.',
'Lift your shoulders upward.',
),
'squat': _squat,
'leg_press': ExerciseMuscleMotion(
['quads', 'glutes'],
'Bring the platform toward you as your knees and hips bend.',
'Press the platform away by extending your knees and hips.',
),
'leg_extension': ExerciseMuscleMotion(
['quads'],
'Bend your knees as you lower the weight.',
'Straighten your knees against resistance.',
),
'leg_curl': ExerciseMuscleMotion(
['hamstrings'],
'Extend your knees as you lower the weight.',
'Bend your knees against resistance.',
),
'romanian_deadlift': ExerciseMuscleMotion(
['hamstrings', 'glutes'],
'Hinge at your hips, moving them back as you lower the weight.',
'Extend your hips to stand upright.',
),
'hip_thrust': ExerciseMuscleMotion(
['glutes'],
'Lower your hips with control.',
'Drive your hips upward.',
),
'calf_raise': ExerciseMuscleMotion(
['calves'],
'Lower your heels with control.',
'Raise your heels by pushing through your forefoot.',
),
'bicep_curl': _curl,
'hammer_curl': _curl,
'preacher_curl': _curl,
'concentration_curl': _curl,
'tricep_pushdown': _triceps,
'skull_crushers': _triceps,
'overhead_tricep_extension': _triceps,
'crunches': ExerciseMuscleMotion(
['core'],
'Uncurl your torso with control.',
'Curl your torso toward your pelvis.',
),
'cable_crunch': ExerciseMuscleMotion(
['core'],
'Uncurl your torso with control.',
'Curl your torso against resistance.',
),
};
56 changes: 43 additions & 13 deletions workout-logger/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';

Expand Down Expand Up @@ -91,6 +92,12 @@ Future<void> _resolveStorageBackend() async {
void main() async {
DebugLogBuffer.attach();
WidgetsFlutterBinding.ensureInitialized();
LicenseRegistry.addLicense(() async* {
yield LicenseEntryWithLineBreaks(
['MuscleMap body geometry'],
await rootBundle.loadString('THIRD_PARTY_NOTICES.md'),
);
});

// Set preferred orientations
await SystemChrome.setPreferredOrientations([
Expand Down Expand Up @@ -263,6 +270,10 @@ class AppInitializer extends StatefulWidget {
class _AppInitializerState extends State<AppInitializer> {
bool _initialized = false;
bool _needsNamePrompt = false;
bool _needsGenderPrompt = false;
String? _pendingVersion;
String? _previousVersion;
bool _versionChanged = false;
String? _error;

@override
Expand Down Expand Up @@ -330,28 +341,40 @@ class _AppInitializerState extends State<AppInitializer> {
setState(() {
_initialized = true;
_needsNamePrompt = needsName;
_needsGenderPrompt = !needsName && settings.needsGenderSelection;
_pendingVersion = version;
_previousVersion = settings.lastSeenVersion;
_versionChanged = versionChanged;
});

if (!needsName && settings.lastSeenVersion == null) {
await settings.markVersionSeen(version);
}
if (!needsName && versionChanged) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!mounted) return;
await showVersionUpdateSheet(
context,
version,
previousVersion: settings.lastSeenVersion,
);
if (mounted) await settings.markVersionSeen(version);
});
if (!needsName && !_needsGenderPrompt) {
_finishUpgrade();
}
} catch (e) {
if (!mounted) return;
setState(() => _error = e.toString());
}
}

void _finishUpgrade() {
final version = _pendingVersion;
if (version == null) return;
_pendingVersion = null;
if (!_versionChanged && _previousVersion != null) return;
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!mounted) return;
final settings = context.read<SettingsProvider>();
try {
if (_versionChanged) {
await showVersionUpdateSheet(context, version, previousVersion: _previousVersion);
}
if (mounted) await settings.markVersionSeen(version);
} catch (e, st) {
debugPrint('Finishing upgrade failed: $e\n$st');
}
});
}

@override
Widget build(BuildContext context) {
if (_error != null) {
Expand Down Expand Up @@ -422,6 +445,13 @@ class _AppInitializerState extends State<AppInitializer> {
);
}

if (_needsGenderPrompt) {
return GenderSetupPage(onComplete: () {
setState(() => _needsGenderPrompt = false);
_finishUpgrade();
});
}

return const HomeScreen();
}
}
Loading
Loading