diff --git a/assets/images/bonus_animation/android.png b/assets/images/bonus_animation/android.png new file mode 100644 index 00000000..200e1c6c Binary files /dev/null and b/assets/images/bonus_animation/android.png differ diff --git a/assets/images/bonus_animation/dash_nest.png b/assets/images/bonus_animation/dash_nest.png new file mode 100644 index 00000000..210f17cb Binary files /dev/null and b/assets/images/bonus_animation/dash_nest.png differ diff --git a/assets/images/bonus_animation/dino.png b/assets/images/bonus_animation/dino.png new file mode 100644 index 00000000..a8a9cfe3 Binary files /dev/null and b/assets/images/bonus_animation/dino.png differ diff --git a/assets/images/bonus_animation/google.png b/assets/images/bonus_animation/google.png new file mode 100644 index 00000000..7adab3b4 Binary files /dev/null and b/assets/images/bonus_animation/google.png differ diff --git a/assets/images/bonus_animation/sparky_turbo_charge.png b/assets/images/bonus_animation/sparky_turbo_charge.png new file mode 100644 index 00000000..8b3491e8 Binary files /dev/null and b/assets/images/bonus_animation/sparky_turbo_charge.png differ diff --git a/lib/game/view/widgets/bonus_animation.dart b/lib/game/view/widgets/bonus_animation.dart new file mode 100644 index 00000000..39cee913 --- /dev/null +++ b/lib/game/view/widgets/bonus_animation.dart @@ -0,0 +1,106 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flame/flame.dart'; +import 'package:flame/sprite.dart'; +import 'package:flame/widgets.dart'; +import 'package:flutter/material.dart' hide Image; +import 'package:pinball/gen/assets.gen.dart'; + +class BonusAnimation extends StatelessWidget { + const BonusAnimation._( + this.imagePath, { + VoidCallback? onCompleted, + Key? key, + }) : _onCompleted = onCompleted, + super(key: key); + + BonusAnimation.dashNest({ + Key? key, + VoidCallback? onCompleted, + }) : this._( + Assets.images.bonusAnimation.dashNest.keyName, + onCompleted: onCompleted, + key: key, + ); + + BonusAnimation.sparkyTurboCharge({ + Key? key, + VoidCallback? onCompleted, + }) : this._( + Assets.images.bonusAnimation.sparkyTurboCharge.keyName, + onCompleted: onCompleted, + key: key, + ); + + BonusAnimation.dino({ + Key? key, + VoidCallback? onCompleted, + }) : this._( + Assets.images.bonusAnimation.dino.keyName, + onCompleted: onCompleted, + key: key, + ); + + BonusAnimation.android({ + Key? key, + VoidCallback? onCompleted, + }) : this._( + Assets.images.bonusAnimation.android.keyName, + onCompleted: onCompleted, + key: key, + ); + + BonusAnimation.google({ + Key? key, + VoidCallback? onCompleted, + }) : this._( + Assets.images.bonusAnimation.google.keyName, + onCompleted: onCompleted, + key: key, + ); + + final String imagePath; + + final VoidCallback? _onCompleted; + + static Future loadAssets() { + Flame.images.prefix = ''; + return Flame.images.loadAll([ + Assets.images.bonusAnimation.dashNest.keyName, + Assets.images.bonusAnimation.sparkyTurboCharge.keyName, + Assets.images.bonusAnimation.dino.keyName, + Assets.images.bonusAnimation.android.keyName, + Assets.images.bonusAnimation.google.keyName, + ]); + } + + @override + Widget build(BuildContext context) { + final spriteSheet = SpriteSheet.fromColumnsAndRows( + image: Flame.images.fromCache(imagePath), + columns: 8, + rows: 9, + ); + final animation = spriteSheet.createAnimation( + row: 0, + stepTime: 1 / 24, + to: spriteSheet.rows * spriteSheet.columns, + loop: false, + ); + + Future.delayed( + Duration(seconds: animation.totalDuration().ceil()), + () { + _onCompleted?.call(); + }, + ); + + return SizedBox( + width: double.infinity, + height: double.infinity, + child: SpriteAnimationWidget( + animation: animation, + ), + ); + } +} diff --git a/lib/game/view/widgets/widgets.dart b/lib/game/view/widgets/widgets.dart index 7e9db5c3..674577af 100644 --- a/lib/game/view/widgets/widgets.dart +++ b/lib/game/view/widgets/widgets.dart @@ -1,2 +1,3 @@ +export 'bonus_animation.dart'; export 'game_hud.dart'; export 'play_button_overlay.dart'; diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index 97be7f3e..3e52e399 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -10,10 +10,36 @@ import 'package:flutter/widgets.dart'; class $AssetsImagesGen { const $AssetsImagesGen(); + $AssetsImagesBonusAnimationGen get bonusAnimation => + const $AssetsImagesBonusAnimationGen(); $AssetsImagesComponentsGen get components => const $AssetsImagesComponentsGen(); } +class $AssetsImagesBonusAnimationGen { + const $AssetsImagesBonusAnimationGen(); + + /// File path: assets/images/bonus_animation/android.png + AssetGenImage get android => + const AssetGenImage('assets/images/bonus_animation/android.png'); + + /// File path: assets/images/bonus_animation/dash_nest.png + AssetGenImage get dashNest => + const AssetGenImage('assets/images/bonus_animation/dash_nest.png'); + + /// File path: assets/images/bonus_animation/dino.png + AssetGenImage get dino => + const AssetGenImage('assets/images/bonus_animation/dino.png'); + + /// File path: assets/images/bonus_animation/google.png + AssetGenImage get google => + const AssetGenImage('assets/images/bonus_animation/google.png'); + + /// File path: assets/images/bonus_animation/sparky_turbo_charge.png + AssetGenImage get sparkyTurboCharge => const AssetGenImage( + 'assets/images/bonus_animation/sparky_turbo_charge.png'); +} + class $AssetsImagesComponentsGen { const $AssetsImagesComponentsGen(); diff --git a/pubspec.yaml b/pubspec.yaml index f7676247..f17ea07a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,7 @@ flutter: assets: - assets/images/components/ + - assets/images/bonus_animation/ flutter_gen: line_length: 80 diff --git a/test/game/view/pinball_game_page_test.dart b/test/game/view/pinball_game_page_test.dart index 3c54197a..bbed2963 100644 --- a/test/game/view/pinball_game_page_test.dart +++ b/test/game/view/pinball_game_page_test.dart @@ -160,6 +160,10 @@ void main() { }); group('PinballGameView', () { + setUp(() async { + await Future.wait(game.preLoadAssets()); + }); + testWidgets('renders game and a hud', (tester) async { final gameBloc = MockGameBloc(); whenListen( diff --git a/test/game/view/widgets/bonus_animation_test.dart b/test/game/view/widgets/bonus_animation_test.dart new file mode 100644 index 00000000..9c23ae0d --- /dev/null +++ b/test/game/view/widgets/bonus_animation_test.dart @@ -0,0 +1,99 @@ +import 'dart:async'; + +import 'dart:ui' as ui; + +import 'package:flame/assets.dart'; +import 'package:flame/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/view/widgets/bonus_animation.dart'; + +import '../../../helpers/helpers.dart'; + +class MockImages extends Mock implements Images {} + +class MockImage extends Mock implements ui.Image {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() async { + await BonusAnimation.loadAssets(); + }); + + group('loads SpriteAnimationWidget correctly for', () { + testWidgets('dashNest', (tester) async { + await tester.pumpApp( + BonusAnimation.dashNest(), + ); + await tester.pump(); + + expect(find.byType(SpriteAnimationWidget), findsOneWidget); + }); + + testWidgets('dino', (tester) async { + await tester.pumpApp( + BonusAnimation.dino(), + ); + await tester.pump(); + + expect(find.byType(SpriteAnimationWidget), findsOneWidget); + }); + + testWidgets('sparkyTurboCharge', (tester) async { + await tester.pumpApp( + BonusAnimation.sparkyTurboCharge(), + ); + await tester.pump(); + + expect(find.byType(SpriteAnimationWidget), findsOneWidget); + }); + + testWidgets('google', (tester) async { + await tester.pumpApp( + BonusAnimation.google(), + ); + await tester.pump(); + + expect(find.byType(SpriteAnimationWidget), findsOneWidget); + }); + + testWidgets('android', (tester) async { + await tester.pumpApp( + BonusAnimation.android(), + ); + await tester.pump(); + + expect(find.byType(SpriteAnimationWidget), findsOneWidget); + }); + }); + + // TODO(arturplaczek): refactor this test when there is a new version of the + // flame with an onComplete callback in SpriteAnimationWidget + // https://github.com/flame-engine/flame/issues/1543 + testWidgets('called onCompleted callback at the end of animation ', + (tester) async { + final completer = Completer(); + + await tester.runAsync(() async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BonusAnimation.dashNest( + onCompleted: completer.complete, + ), + ), + ), + ); + + await tester.pump(); + + await Future.delayed(const Duration(seconds: 4)); + + await tester.pump(); + + expect(completer.isCompleted, isTrue); + }); + }); +}