From 2ecb578eb07d9d7a27aab6dcebdea34bc1c18d56 Mon Sep 17 00:00:00 2001 From: mickaelbegon Date: Wed, 10 Jun 2026 08:07:20 -0400 Subject: [PATCH] Fix web jump image updates --- lib/main.dart | 44 +++++++++++++++------------- lib/providers/biomechanics.dart | 2 +- lib/widgets/background_image.dart | 6 +++- lib/widgets/skater_image.dart | 48 ++++++++++++++++++++----------- test/background_image_test.dart | 32 +++++++++++++++++++++ test/biomechanics_test.dart | 12 ++++++++ web/index.html | 27 ++--------------- 7 files changed, 109 insertions(+), 62 deletions(-) create mode 100644 test/background_image_test.dart diff --git a/lib/main.dart b/lib/main.dart index 80f022d..216b71a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:jump_app/screens/terms_and_services_screen.dart'; import 'package:provider/provider.dart'; @@ -11,9 +12,11 @@ import '/screens/jump_screen.dart'; void main() async { // Turn off portrait mode WidgetsFlutterBinding.ensureInitialized(); - await SystemChrome.setPreferredOrientations([ - DeviceOrientation.landscapeRight, - ]); + if (!kIsWeb) { + await SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeRight, + ]); + } runApp(const MyApp()); } @@ -25,22 +28,23 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { final app = AppParameters(); return MultiProvider( - providers: [ - ChangeNotifierProvider(create: (ctx) => LocaleText(language: 'Fr')), - ChangeNotifierProvider( - create: (ctx) => - Biomechanics(app.jumpDescription.bounds.initial)), - ChangeNotifierProvider(create: (ctx) => app), - ], - child: MaterialApp( - title: 'Jump app', - debugShowCheckedModeBanner: false, - routes: { - TermsAndServicesScreen.route: (context) => - const TermsAndServicesScreen(), - JumpScreen.route: (context) => const JumpScreen() - }, - initialRoute: TermsAndServicesScreen.route, - )); + providers: [ + ChangeNotifierProvider(create: (ctx) => LocaleText(language: 'Fr')), + ChangeNotifierProvider( + create: (ctx) => Biomechanics(app.jumpDescription.bounds.initial), + ), + ChangeNotifierProvider(create: (ctx) => app), + ], + child: MaterialApp( + title: 'Jump app', + debugShowCheckedModeBanner: false, + routes: { + TermsAndServicesScreen.route: (context) => + const TermsAndServicesScreen(), + JumpScreen.route: (context) => const JumpScreen(), + }, + initialRoute: TermsAndServicesScreen.route, + ), + ); } } diff --git a/lib/providers/biomechanics.dart b/lib/providers/biomechanics.dart index c737771..0b7dc5d 100644 --- a/lib/providers/biomechanics.dart +++ b/lib/providers/biomechanics.dart @@ -73,7 +73,7 @@ class Biomechanics with ChangeNotifier { notifyListeners(); } - late final double _finalInertia; // kg.m^2 + late double _finalInertia; // kg.m^2 double get finalInertia => _finalInertia; set finalInertia(double value) { _finalInertia = MathHelper.roundToPrecision(value, 2); diff --git a/lib/widgets/background_image.dart b/lib/widgets/background_image.dart index d010ac3..2e8add5 100644 --- a/lib/widgets/background_image.dart +++ b/lib/widgets/background_image.dart @@ -13,6 +13,10 @@ class BackgoundImage extends StatelessWidget { @override Widget build(BuildContext context) { return Positioned( - left: 0, right: 0, bottom: floor, child: Image.asset(imagePath)); + left: 0, + right: 0, + bottom: floor, + child: Image.asset(imagePath, key: ValueKey(imagePath)), + ); } } diff --git a/lib/widgets/skater_image.dart b/lib/widgets/skater_image.dart index 56fd853..43c0331 100644 --- a/lib/widgets/skater_image.dart +++ b/lib/widgets/skater_image.dart @@ -18,10 +18,7 @@ import 'pushoff_phase.dart'; import 'total_rotation.dart'; class SkaterImage extends StatefulWidget { - const SkaterImage({ - super.key, - this.width, - }); + const SkaterImage({super.key, this.width}); final double? width; @@ -43,20 +40,30 @@ class _SkaterImageState extends State { final jumpHeigh = 0.100 * w; final comStart = Offset(0.265 * w, -floor - 0.100 * w); - final comSliderStartPosition = - Offset(comStart.dx - 0.09 * w, floor + 0.045 * w); + final comSliderStartPosition = Offset( + comStart.dx - 0.09 * w, + floor + 0.045 * w, + ); final comFinal = Offset(0.791 * w, -floor - 0.115 * w); - final comSliderFinalPosition = - Offset(comFinal.dx + 0.03 * w, floor + 0.045 * w); + final comSliderFinalPosition = Offset( + comFinal.dx + 0.03 * w, + floor + 0.045 * w, + ); final comSliderHeight = 0.15 * w; final comMid = Offset( - (comStart.dx + comFinal.dx) / 2, (comStart.dy + comFinal.dy) / 2); + (comStart.dx + comFinal.dx) / 2, + (comStart.dy + comFinal.dy) / 2, + ); final comSize = 0.011 * w; - final rotationPosition = - Offset(comFinal.dx - 0.05 * w, comFinal.dy - 0.1 * w); - final maximalRotationPosition = - Offset(comMid.dx - 0.07 * w, comFinal.dy - 0.02 * w); + final rotationPosition = Offset( + comFinal.dx - 0.05 * w, + comFinal.dy - 0.1 * w, + ); + final maximalRotationPosition = Offset( + comMid.dx - 0.07 * w, + comFinal.dy - 0.02 * w, + ); final initialRotationSliderPosition = Offset(0.14 * w, 0.105 * w + floor); final initialRotationSliderSize = 0.20 * w; @@ -91,7 +98,11 @@ class _SkaterImageState extends State { alignment: Alignment.bottomLeft, children: [ const Header(), - BackgoundImage(floor: floor, imagePath: app.jumpDescription.path), + BackgoundImage( + key: ValueKey(app.jumpDescription.path), + floor: floor, + imagePath: app.jumpDescription.path, + ), if (isTranslation) GroundReactionForce( arrowHead: grfArrow, @@ -126,7 +137,9 @@ class _SkaterImageState extends State { MaximalRotationVelocity(position: maximalRotationPosition), if (isTranslation) FlightApex( - apex: Offset(comMid.dx, comMid.dy - jumpHeigh), floor: floor), + apex: Offset(comMid.dx, comMid.dy - jumpHeigh), + floor: floor, + ), TotalRotation(position: rotationPosition), if (isRotation) FlightInertia( @@ -153,7 +166,10 @@ class _SkaterImageState extends State { ), if (!isTranslation && !isRotation) LandingPhase( - comFinal: comFinal, arrowsBelow: arrowsBelow, land: land), + comFinal: comFinal, + arrowsBelow: arrowsBelow, + land: land, + ), if (isRotation) PreJumpRotation( initialRotationSliderPosition: initialRotationSliderPosition, diff --git a/test/background_image_test.dart b/test/background_image_test.dart new file mode 100644 index 0000000..c47f89c --- /dev/null +++ b/test/background_image_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:jump_app/widgets/background_image.dart'; + +void main() { + Widget buildFrame(String imagePath) { + return Directionality( + textDirection: TextDirection.ltr, + child: Stack(children: [BackgoundImage(floor: 10, imagePath: imagePath)]), + ); + } + + testWidgets('updates the displayed asset when the image path changes', ( + tester, + ) async { + const axelPath = 'assets/images/key_frames_axel5.png'; + const lutzPath = 'assets/images/key_frames_lutz4.png'; + + await tester.pumpWidget(buildFrame(axelPath)); + + var image = tester.widget(find.byType(Image)); + expect(image.key, const ValueKey(axelPath)); + expect((image.image as AssetImage).assetName, axelPath); + + await tester.pumpWidget(buildFrame(lutzPath)); + await tester.pump(); + + image = tester.widget(find.byType(Image)); + expect(image.key, const ValueKey(lutzPath)); + expect((image.image as AssetImage).assetName, lutzPath); + }); +} diff --git a/test/biomechanics_test.dart b/test/biomechanics_test.dart index 8298b5b..4fd47b1 100644 --- a/test/biomechanics_test.dart +++ b/test/biomechanics_test.dart @@ -31,6 +31,7 @@ void main() { biomechanics.finalHeight = 1.244; biomechanics.initialInertia = 2.126; biomechanics.minimumInertia = 0.756; + biomechanics.finalInertia = 2.126; biomechanics.timeToMinimumInertia = 0.1806; biomechanics.timeToFinalInertia = 0.0204; biomechanics.initialRotation = 0.256 * 2 * pi; @@ -42,6 +43,7 @@ void main() { expect(biomechanics.finalHeight, 1.24); expect(biomechanics.initialInertia, 2.13); expect(biomechanics.minimumInertia, 0.76); + expect(biomechanics.finalInertia, 2.13); expect(biomechanics.timeToMinimumInertia * 1000, 181); expect(biomechanics.timeToFinalInertia * 1000, 20); expect(biomechanics.initialRotation / 2 / pi, closeTo(0.26, 1e-12)); @@ -55,6 +57,16 @@ void main() { }); group('Biomechanics calculations', () { + test('can reset values when the selected jump changes', () { + final biomechanics = Biomechanics(JumpDescription.axel.bounds.initial); + + expect( + () => biomechanics.setValues(JumpDescription.somersault.bounds.initial), + returnsNormally, + ); + expect(biomechanics.finalInertia, 10); + }); + test('force in bodyweights uses the selected athlete mass', () { final biomechanics = Biomechanics(JumpDescription.lutz.bounds.initial); diff --git a/web/index.html b/web/index.html index 42f5e80..739fa19 100644 --- a/web/index.html +++ b/web/index.html @@ -18,10 +18,10 @@ - + - + @@ -31,29 +31,8 @@ jump_app - - - - - +