diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 82d8fae0..f260a028 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -21,7 +21,7 @@ jobs: - name: Build Flutter App run: | flutter packages get - flutter build web --target lib/main_development.dart --web-renderer canvaskit --release + flutter build web --target lib/main.dart --web-renderer canvaskit --release - name: Deploy to Firebase uses: FirebaseExtended/action-hosting-deploy@v0 diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 5bef442d..48ad112c 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -14,3 +14,4 @@ jobs: flutter_version: 2.10.5 coverage_excludes: "lib/gen/*.dart" test_optimization: false + diff --git a/.github/workflows/spell_check.yaml b/.github/workflows/spell_check.yaml new file mode 100644 index 00000000..bfef58f8 --- /dev/null +++ b/.github/workflows/spell_check.yaml @@ -0,0 +1,21 @@ +name: spell_check + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: [pull_request] + +jobs: + build: + name: Spell Check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Check spelling + uses: zwaldowski/cspell-action@v1 + with: + paths: '**/*.{dart,arb,md}' + config: .vscode/cspell.json \ No newline at end of file diff --git a/.idea/runConfigurations/development.xml b/.idea/runConfigurations/development.xml deleted file mode 100644 index 37d45fe3..00000000 --- a/.idea/runConfigurations/development.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/main.xml b/.idea/runConfigurations/main.xml new file mode 100644 index 00000000..b5c260ef --- /dev/null +++ b/.idea/runConfigurations/main.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/production.xml b/.idea/runConfigurations/production.xml deleted file mode 100644 index e55961d8..00000000 --- a/.idea/runConfigurations/production.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/staging.xml b/.idea/runConfigurations/staging.xml deleted file mode 100644 index 3f7d6219..00000000 --- a/.idea/runConfigurations/staging.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.vscode/cspell.json b/.vscode/cspell.json new file mode 100644 index 00000000..71c54903 --- /dev/null +++ b/.vscode/cspell.json @@ -0,0 +1,50 @@ +{ + "version": "0.2", + "enabled": true, + "language": "en", + "words": [ + "animatronic", + "argb", + "audioplayers", + "backbox", + "bezier", + "contador", + "cupertino", + "dashbook", + "deserialization", + "dpad", + "endtemplate", + "firestore", + "gapless", + "genhtml", + "goldens", + "lcov", + "leaderboard", + "loadables", + "localizable", + "mixins", + "mocktail", + "mostrado", + "multiball", + "multiballs", + "occluder", + "pรกgina", + "pixelated", + "pixeloid", + "rects", + "rrect", + "serializable", + "sparky's", + "tappable", + "tappables", + "texto", + "theming", + "unawaited", + "unfocus", + "unlayered", + "vsync" + ], + "ignorePaths": [ + ".github/workflows/**" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json index 1b855b10..e2ea6cee 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,30 +5,10 @@ "version": "0.2.0", "configurations": [ { - "name": "Launch development", + "name": "Launch pinball", "request": "launch", "type": "dart", - "program": "lib/main_development.dart", - "args": [ - "--flavor", - "development", - "--target", - "lib/main_development.dart" - ] - }, - { - "name": "Launch staging", - "request": "launch", - "type": "dart", - "program": "lib/main_staging.dart", - "args": ["--flavor", "staging", "--target", "lib/main_staging.dart"] - }, - { - "name": "Launch production", - "request": "launch", - "type": "dart", - "program": "lib/main_production.dart", - "args": ["--flavor", "production", "--target", "lib/main_production.dart"] + "program": "lib/main.dart" }, { "name": "Launch component sandbox", diff --git a/README.md b/README.md index b91a1c98..957cb05b 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,31 @@ -# Pinball +# I/O Pinball +[![Pinball Header][logo]][pinball_link] + +[![io_pinball][build_status_badge]][workflow_link] ![coverage][coverage_badge] [![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] [![License: MIT][license_badge]][license_link] -Generated by the [Very Good CLI][very_good_cli_link] ๐Ÿค– +A Pinball game built with [Flutter][flutter_link] and [Firebase][firebase_link] for [Google I/O 2022][google_io_link]. -Google I/O 2022 Pinball game built with Flutter and Firebase +[Try it now][pinball_link] and [learn about how it's made][blog_link]. ---- +_Built by [Very Good Ventures][very_good_ventures_link] in partnership with Google_ -## Getting Started ๐Ÿš€ +_Created using [Very Good CLI][very_good_cli_link] ๐Ÿค–_ -This project contains 3 flavors: +--- -- development -- staging -- production +## Getting Started ๐Ÿš€ -To run the desired flavor either use the launch configuration in VSCode/Android Studio or use the following commands: +To run the desired project either use the launch configuration in VSCode/Android Studio or use the following commands: ```sh -# Development -$ flutter run --flavor development --target lib/main_development.dart - -# Staging -$ flutter run --flavor staging --target lib/main_staging.dart - -# Production -$ flutter run --flavor production --target lib/main_production.dart +$ flutter run -d chrome ``` -_\*Pinball works on iOS, Android, Web, and Windows._ +_\*I/O Pinball works on Web for desktop and mobile._ --- @@ -48,7 +42,6 @@ To view the generated coverage report you can use [lcov](https://github.com/linu ```sh # Generate Coverage Report $ genhtml coverage/lcov.info -o coverage/ - # Open Coverage Report $ open coverage/index.html ``` @@ -101,22 +94,6 @@ Widget build(BuildContext context) { } ``` -### Adding Supported Locales - -Update the `CFBundleLocalizations` array in the `Info.plist` at `ios/Runner/Info.plist` to include the new locale. - -```xml - ... - - CFBundleLocalizations - - en - es - - - ... -``` - ### Adding Translations 1. For each supported locale, add a new ARB file in `lib/l10n/arb`. @@ -154,38 +131,20 @@ Update the `CFBundleLocalizations` array in the `Info.plist` at `ios/Runner/Info } ``` -### Deploy application to Firebase hosting - -Follow the following steps to deploy the application. - -## Firebase CLI - -Install and authenticate with [Firebase CLI tools](https://firebase.google.com/docs/cli) - -## Build the project using the desired environment - -```bash -# Development -$ flutter build web --release --target lib/main_development.dart - -# Staging -$ flutter build web --release --target lib/main_staging.dart - -# Production -$ flutter build web --release --target lib/main_production.dart -``` - -## Deploy - -```bash -$ firebase deploy -``` - +[build_status_badge]: https://github.com/flutter/pinball/actions/workflows/main.yaml/badge.svg [coverage_badge]: coverage_badge.svg +[firebase_link]: https://firebase.google.com/ +[flutter_link]: https://flutter.dev [flutter_localizations_link]: https://api.flutter.dev/flutter/flutter_localizations/flutter_localizations-library.html +[google_io_link]: https://events.google.com/io/ +[blog_link]: https://medium.com/flutter/i-o-pinball-powered-by-flutter-and-firebase-d22423f3f5d [internationalization_link]: https://flutter.dev/docs/development/accessibility-and-localization/internationalization [license_badge]: https://img.shields.io/badge/license-MIT-blue.svg [license_link]: https://opensource.org/licenses/MIT +[logo]: art/readme_header.png +[pinball_link]: https://pinball.flutter.dev [very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg [very_good_analysis_link]: https://pub.dev/packages/very_good_analysis [very_good_cli_link]: https://github.com/VeryGoodOpenSource/very_good_cli +[very_good_ventures_link]: https://verygood.ventures/ +[workflow_link]: https://github.com/flutter/pinball/actions/workflows/main.yaml diff --git a/analysis_options.yaml b/analysis_options.yaml index f8155aa6..eb141d0e 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,3 +2,6 @@ include: package:very_good_analysis/analysis_options.2.4.0.yaml analyzer: exclude: - lib/**/*.gen.dart +linter: + rules: + public_member_api_docs: false \ No newline at end of file diff --git a/art/readme_header.png b/art/readme_header.png new file mode 100644 index 00000000..43e7fb53 Binary files /dev/null and b/art/readme_header.png differ diff --git a/assets/images/bonus_animation/android_spaceship.png b/assets/images/bonus_animation/android_spaceship.png index 200e1c6c..66cc8b6a 100644 Binary files a/assets/images/bonus_animation/android_spaceship.png and b/assets/images/bonus_animation/android_spaceship.png differ diff --git a/assets/images/bonus_animation/dash_nest.png b/assets/images/bonus_animation/dash_nest.png index 210f17cb..42aa8b9e 100644 Binary files a/assets/images/bonus_animation/dash_nest.png and b/assets/images/bonus_animation/dash_nest.png differ diff --git a/assets/images/bonus_animation/dino_chomp.png b/assets/images/bonus_animation/dino_chomp.png index a8a9cfe3..c6825c0b 100644 Binary files a/assets/images/bonus_animation/dino_chomp.png and b/assets/images/bonus_animation/dino_chomp.png differ diff --git a/assets/images/bonus_animation/google_word.png b/assets/images/bonus_animation/google_word.png index c4ab2948..fa07da65 100644 Binary files a/assets/images/bonus_animation/google_word.png and b/assets/images/bonus_animation/google_word.png differ diff --git a/assets/images/bonus_animation/sparky_turbo_charge.png b/assets/images/bonus_animation/sparky_turbo_charge.png index 8b3491e8..c5d2d9d3 100644 Binary files a/assets/images/bonus_animation/sparky_turbo_charge.png and b/assets/images/bonus_animation/sparky_turbo_charge.png differ diff --git a/assets/images/link_box/info_icon.png b/assets/images/link_box/info_icon.png deleted file mode 100644 index 78556be0..00000000 Binary files a/assets/images/link_box/info_icon.png and /dev/null differ diff --git a/assets/images/loading_game/io_pinball.png b/assets/images/loading_game/io_pinball.png new file mode 100644 index 00000000..c8d9fadc Binary files /dev/null and b/assets/images/loading_game/io_pinball.png differ diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index ae3094e1..f621f1e2 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:authentication_repository/authentication_repository.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -11,21 +9,25 @@ import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/start_game.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_ui/pinball_ui.dart'; +import 'package:share_repository/share_repository.dart'; class App extends StatelessWidget { const App({ Key? key, required AuthenticationRepository authenticationRepository, required LeaderboardRepository leaderboardRepository, - required PinballPlayer pinballPlayer, + required ShareRepository shareRepository, + required PinballAudioPlayer pinballAudioPlayer, }) : _authenticationRepository = authenticationRepository, _leaderboardRepository = leaderboardRepository, - _pinballPlayer = pinballPlayer, + _shareRepository = shareRepository, + _pinballAudioPlayer = pinballAudioPlayer, super(key: key); final AuthenticationRepository _authenticationRepository; final LeaderboardRepository _leaderboardRepository; - final PinballPlayer _pinballPlayer; + final ShareRepository _shareRepository; + final PinballAudioPlayer _pinballAudioPlayer; @override Widget build(BuildContext context) { @@ -33,7 +35,8 @@ class App extends StatelessWidget { providers: [ RepositoryProvider.value(value: _authenticationRepository), RepositoryProvider.value(value: _leaderboardRepository), - RepositoryProvider.value(value: _pinballPlayer), + RepositoryProvider.value(value: _shareRepository), + RepositoryProvider.value(value: _pinballAudioPlayer), ], child: MultiBlocProvider( providers: [ diff --git a/lib/assets_manager/cubit/assets_manager_cubit.dart b/lib/assets_manager/cubit/assets_manager_cubit.dart index b97483d4..eb0f7e31 100644 --- a/lib/assets_manager/cubit/assets_manager_cubit.dart +++ b/lib/assets_manager/cubit/assets_manager_cubit.dart @@ -1,27 +1,39 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; +import 'package:pinball_audio/pinball_audio.dart'; part 'assets_manager_state.dart'; -/// {@template assets_manager_cubit} -/// Cubit responsable for pre loading any game assets -/// {@endtemplate} class AssetsManagerCubit extends Cubit { - /// {@macro assets_manager_cubit} - AssetsManagerCubit(List loadables) - : super( - AssetsManagerState.initial( - loadables: loadables, - ), - ); + AssetsManagerCubit(this._game, this._audioPlayer) + : super(const AssetsManagerState.initial()); + + final PinballGame _game; + final PinballAudioPlayer _audioPlayer; - /// Loads the assets Future load() async { + /// Assigning loadables is a very expensive operation. With this purposeful + /// delay here, which is a bit random in duration but enough to let the UI + /// do its job without adding too much delay for the user, we are letting + /// the UI paint first, and then we start loading the assets. + await Future.delayed(const Duration(seconds: 1)); + emit( + state.copyWith( + loadables: [ + _game.preFetchLeaderboard(), + ..._game.preLoadAssets(), + ..._audioPlayer.load(), + ...BonusAnimation.loadAssets(), + ...SelectedCharacter.loadAssets(), + ], + ), + ); final all = state.loadables.map((loadable) async { await loadable; emit(state.copyWith(loaded: [...state.loaded, loadable])); }).toList(); - await Future.wait(all); } } diff --git a/lib/assets_manager/cubit/assets_manager_state.dart b/lib/assets_manager/cubit/assets_manager_state.dart index 8ef1e874..4847adc6 100644 --- a/lib/assets_manager/cubit/assets_manager_state.dart +++ b/lib/assets_manager/cubit/assets_manager_state.dart @@ -11,9 +11,8 @@ class AssetsManagerState extends Equatable { }); /// {@macro assets_manager_state} - const AssetsManagerState.initial({ - required List loadables, - }) : this(loadables: loadables, loaded: const []); + const AssetsManagerState.initial() + : this(loadables: const [], loaded: const []); /// List of futures to load final List loadables; @@ -22,7 +21,11 @@ class AssetsManagerState extends Equatable { final List loaded; /// Returns a value between 0 and 1 to indicate the loading progress - double get progress => loaded.length / loadables.length; + double get progress => + loadables.isEmpty ? 0 : loaded.length / loadables.length; + + /// Only returns false if all the assets have been loaded + bool get isLoading => progress != 1; /// Returns a copy of this instance with the given parameters /// updated diff --git a/lib/assets_manager/views/assets_loading_page.dart b/lib/assets_manager/views/assets_loading_page.dart index ddb76803..72476064 100644 --- a/lib/assets_manager/views/assets_loading_page.dart +++ b/lib/assets_manager/views/assets_loading_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pinball/assets_manager/assets_manager.dart'; +import 'package:pinball/gen/gen.dart'; import 'package:pinball/l10n/l10n.dart'; import 'package:pinball_ui/pinball_ui.dart'; @@ -16,30 +17,32 @@ class AssetsLoadingPage extends StatelessWidget { Widget build(BuildContext context) { final l10n = context.l10n; final headline1 = Theme.of(context).textTheme.headline1; - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - l10n.ioPinball, - style: headline1!.copyWith(fontSize: 80), - textAlign: TextAlign.center, - ), - const SizedBox(height: 40), - AnimatedEllipsisText( - l10n.loading, - style: headline1, - ), - const SizedBox(height: 40), - FractionallySizedBox( - widthFactor: 0.8, - child: BlocBuilder( - builder: (context, state) { - return PinballLoadingIndicator(value: state.progress); - }, + return Container( + decoration: const CrtBackground(), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Assets.images.loadingGame.ioPinball.image(), ), - ), - ], + const SizedBox(height: 40), + AnimatedEllipsisText( + l10n.loading, + style: headline1, + ), + const SizedBox(height: 40), + FractionallySizedBox( + widthFactor: 0.8, + child: BlocBuilder( + builder: (context, state) { + return PinballLoadingIndicator(value: state.progress); + }, + ), + ), + ], + ), ), ); } diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index c5e42951..f4028ee1 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:async'; import 'dart:developer'; diff --git a/lib/game/behaviors/ball_spawning_behavior.dart b/lib/game/behaviors/ball_spawning_behavior.dart index 75656d8f..8995c16b 100644 --- a/lib/game/behaviors/ball_spawning_behavior.dart +++ b/lib/game/behaviors/ball_spawning_behavior.dart @@ -1,9 +1,9 @@ import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; /// Spawns a new [Ball] into the game when all balls are lost and still /// [GameStatus.playing]. @@ -23,7 +23,9 @@ class BallSpawningBehavior extends Component void onNewState(GameState state) { final plunger = gameRef.descendants().whereType().single; final canvas = gameRef.descendants().whereType().single; - final characterTheme = readProvider(); + final characterTheme = readBloc() + .state + .characterTheme; final ball = Ball(assetPath: characterTheme.ball.keyName) ..initialPosition = Vector2( plunger.body.position.x, diff --git a/lib/game/behaviors/behaviors.dart b/lib/game/behaviors/behaviors.dart index c7ad6880..bb196cec 100644 --- a/lib/game/behaviors/behaviors.dart +++ b/lib/game/behaviors/behaviors.dart @@ -1,5 +1,9 @@ export 'ball_spawning_behavior.dart'; +export 'bonus_ball_spawning_behavior.dart'; export 'bonus_noise_behavior.dart'; export 'bumper_noise_behavior.dart'; export 'camera_focusing_behavior.dart'; +export 'character_selection_behavior.dart'; +export 'cow_bumper_noise_behavior.dart'; +export 'kicker_noise_behavior.dart'; export 'scoring_behavior.dart'; diff --git a/lib/game/behaviors/bonus_ball_spawning_behavior.dart b/lib/game/behaviors/bonus_ball_spawning_behavior.dart new file mode 100644 index 00000000..26fe423d --- /dev/null +++ b/lib/game/behaviors/bonus_ball_spawning_behavior.dart @@ -0,0 +1,30 @@ +import 'package:flame/components.dart'; +import 'package:pinball/select_character/select_character.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// {@template bonus_ball_spawning_behavior} +/// After a duration, spawns a bonus ball from the [DinoWalls] and boosts it +/// into the middle of the board. +/// {@endtemplate} +class BonusBallSpawningBehavior extends TimerComponent with HasGameRef { + /// {@macro bonus_ball_spawning_behavior} + BonusBallSpawningBehavior() + : super( + period: 5, + removeOnFinish: true, + ); + + @override + void onTick() { + final characterTheme = readBloc() + .state + .characterTheme; + gameRef.descendants().whereType().single.add( + Ball(assetPath: characterTheme.ball.keyName) + ..add(BallImpulsingBehavior(impulse: Vector2(-40, 0))) + ..initialPosition = Vector2(29.2, -24.5) + ..zIndex = ZIndexes.ballOnBoard, + ); + } +} diff --git a/lib/game/behaviors/bonus_noise_behavior.dart b/lib/game/behaviors/bonus_noise_behavior.dart index 9d67e964..b330dc9f 100644 --- a/lib/game/behaviors/bonus_noise_behavior.dart +++ b/lib/game/behaviors/bonus_noise_behavior.dart @@ -15,7 +15,7 @@ class BonusNoiseBehavior extends Component { }, onNewState: (state) { final bonus = state.bonusHistory.last; - final audioPlayer = readProvider(); + final audioPlayer = readProvider(); switch (bonus) { case GameBonus.googleWord: @@ -25,13 +25,13 @@ class BonusNoiseBehavior extends Component { audioPlayer.play(PinballAudio.sparky); break; case GameBonus.dinoChomp: - // TODO(erickzanardo): Add sound + audioPlayer.play(PinballAudio.dino); break; case GameBonus.androidSpaceship: - // TODO(erickzanardo): Add sound + audioPlayer.play(PinballAudio.android); break; case GameBonus.dashNest: - // TODO(erickzanardo): Add sound + audioPlayer.play(PinballAudio.dash); break; } }, diff --git a/lib/game/behaviors/bumper_noise_behavior.dart b/lib/game/behaviors/bumper_noise_behavior.dart index 9c5da701..4144241a 100644 --- a/lib/game/behaviors/bumper_noise_behavior.dart +++ b/lib/game/behaviors/bumper_noise_behavior.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_flame/pinball_flame.dart'; @@ -8,6 +6,6 @@ class BumperNoiseBehavior extends ContactBehavior { @override void beginContact(Object other, Contact contact) { super.beginContact(other, contact); - readProvider().play(PinballAudio.bumper); + readProvider().play(PinballAudio.bumper); } } diff --git a/lib/game/behaviors/character_selection_behavior.dart b/lib/game/behaviors/character_selection_behavior.dart new file mode 100644 index 00000000..27003d75 --- /dev/null +++ b/lib/game/behaviors/character_selection_behavior.dart @@ -0,0 +1,27 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball/select_character/select_character.dart'; +import 'package:pinball_components/pinball_components.dart'; + +/// Updates the [ArcadeBackground] and launch [Ball] to reflect character +/// selections. +class CharacterSelectionBehavior extends Component + with + FlameBlocListenable, + HasGameRef { + @override + void onNewState(CharacterThemeState state) { + gameRef + .descendants() + .whereType() + .single + .bloc + .onCharacterSelected(state.characterTheme); + gameRef + .descendants() + .whereType() + .single + .bloc + .onCharacterSelected(state.characterTheme); + } +} diff --git a/lib/game/behaviors/cow_bumper_noise_behavior.dart b/lib/game/behaviors/cow_bumper_noise_behavior.dart new file mode 100644 index 00000000..14ad1307 --- /dev/null +++ b/lib/game/behaviors/cow_bumper_noise_behavior.dart @@ -0,0 +1,13 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class CowBumperNoiseBehavior extends ContactBehavior { + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + readProvider().play(PinballAudio.cowMoo); + } +} diff --git a/lib/game/behaviors/kicker_noise_behavior.dart b/lib/game/behaviors/kicker_noise_behavior.dart new file mode 100644 index 00000000..a04ffeff --- /dev/null +++ b/lib/game/behaviors/kicker_noise_behavior.dart @@ -0,0 +1,11 @@ +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class KickerNoiseBehavior extends ContactBehavior { + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + readProvider().play(PinballAudio.kicker); + } +} diff --git a/lib/game/behaviors/scoring_behavior.dart b/lib/game/behaviors/scoring_behavior.dart index 8b403d1e..4d6d8b42 100644 --- a/lib/game/behaviors/scoring_behavior.dart +++ b/lib/game/behaviors/scoring_behavior.dart @@ -15,7 +15,7 @@ import 'package:pinball_flame/pinball_flame.dart'; /// {@endtemplate} class ScoringBehavior extends Component with HasGameRef, FlameBlocReader { - /// {@macto scoring_behavior} + /// {@macro scoring_behavior} ScoringBehavior({ required Points points, required Vector2 position, diff --git a/lib/game/bloc/game_bloc.dart b/lib/game/bloc/game_bloc.dart index b22baa14..c63bf514 100644 --- a/lib/game/bloc/game_bloc.dart +++ b/lib/game/bloc/game_bloc.dart @@ -1,4 +1,3 @@ -// ignore_for_file: public_member_api_docs import 'dart:math' as math; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; @@ -13,11 +12,12 @@ class GameBloc extends Bloc { on(_onScored); on(_onIncreasedMultiplier); on(_onBonusActivated); - on(_onSparkyTurboChargeActivated); on(_onGameOver); on(_onGameStarted); } + static const _maxScore = 9999999999; + void _onGameStarted(GameStarted _, Emitter emit) { emit(state.copyWith(status: GameStatus.playing)); } @@ -27,7 +27,10 @@ class GameBloc extends Bloc { } void _onRoundLost(RoundLost event, Emitter emit) { - final score = state.totalScore + state.roundScore * state.multiplier; + final score = math.min( + state.totalScore + state.roundScore * state.multiplier, + _maxScore, + ); final roundsLeft = math.max(state.rounds - 1, 0); emit( @@ -43,9 +46,11 @@ class GameBloc extends Bloc { void _onScored(Scored event, Emitter emit) { if (state.status.isPlaying) { - emit( - state.copyWith(roundScore: state.roundScore + event.points), + final combinedScore = math.min( + state.totalScore + state.roundScore + event.points, + _maxScore, ); + emit(state.copyWith(roundScore: combinedScore - state.totalScore)); } } @@ -66,18 +71,4 @@ class GameBloc extends Bloc { ), ); } - - Future _onSparkyTurboChargeActivated( - SparkyTurboChargeActivated event, - Emitter emit, - ) async { - emit( - state.copyWith( - bonusHistory: [ - ...state.bonusHistory, - GameBonus.sparkyTurboCharge, - ], - ), - ); - } } diff --git a/lib/game/bloc/game_event.dart b/lib/game/bloc/game_event.dart index 6dba8056..126ba299 100644 --- a/lib/game/bloc/game_event.dart +++ b/lib/game/bloc/game_event.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - part of 'game_bloc.dart'; @immutable @@ -42,13 +40,6 @@ class BonusActivated extends GameEvent { List get props => [bonus]; } -class SparkyTurboChargeActivated extends GameEvent { - const SparkyTurboChargeActivated(); - - @override - List get props => []; -} - /// {@template multiplier_increased_game_event} /// Added when a multiplier is gained. /// {@endtemplate} diff --git a/lib/game/bloc/game_state.dart b/lib/game/bloc/game_state.dart index d0311442..8fcab789 100644 --- a/lib/game/bloc/game_state.dart +++ b/lib/game/bloc/game_state.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - part of 'game_bloc.dart'; /// Defines bonuses that a player can gain during a PinballGame. @@ -7,7 +5,7 @@ enum GameBonus { /// Bonus achieved when the ball activates all Google letters. googleWord, - /// Bonus achieved when the user activates all dash nest bumpers. + /// Bonus achieved when the user activates all dash bumpers. dashNest, /// Bonus achieved when a ball enters Sparky's computer. diff --git a/lib/game/components/android_acres/android_acres.dart b/lib/game/components/android_acres/android_acres.dart index 7f9fff13..fd59ace3 100644 --- a/lib/game/components/android_acres/android_acres.dart +++ b/lib/game/components/android_acres/android_acres.dart @@ -1,6 +1,7 @@ // ignore_for_file: avoid_renaming_method_parameters import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flutter/material.dart'; import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart'; @@ -15,42 +16,44 @@ class AndroidAcres extends Component { AndroidAcres() : super( children: [ - SpaceshipRamp( + FlameBlocProvider( + create: AndroidSpaceshipCubit.new, children: [ - RampShotBehavior( - points: Points.fiveThousand, - ), - RampBonusBehavior( - points: Points.oneMillion, + SpaceshipRamp( + children: [ + RampShotBehavior(points: Points.fiveThousand), + RampBonusBehavior(points: Points.oneMillion), + ], ), + SpaceshipRail(), + AndroidSpaceship(position: Vector2(-26.5, -28.5)), + AndroidAnimatronic( + children: [ + ScoringContactBehavior(points: Points.twoHundredThousand), + ], + )..initialPosition = Vector2(-26, -28.25), + AndroidBumper.a( + children: [ + ScoringContactBehavior(points: Points.twentyThousand), + BumperNoiseBehavior(), + ], + )..initialPosition = Vector2(-25.2, 1.5), + AndroidBumper.b( + children: [ + ScoringContactBehavior(points: Points.twentyThousand), + BumperNoiseBehavior(), + ], + )..initialPosition = Vector2(-32.9, -9.3), + AndroidBumper.cow( + children: [ + ScoringContactBehavior(points: Points.twentyThousand), + BumperNoiseBehavior(), + CowBumperNoiseBehavior(), + ], + )..initialPosition = Vector2(-20.7, -13), + AndroidSpaceshipBonusBehavior(), ], ), - SpaceshipRail(), - AndroidSpaceship(position: Vector2(-26.5, -28.5)), - AndroidAnimatronic( - children: [ - ScoringContactBehavior(points: Points.twoHundredThousand), - ], - )..initialPosition = Vector2(-26, -28.25), - AndroidBumper.a( - children: [ - ScoringContactBehavior(points: Points.twentyThousand), - BumperNoiseBehavior(), - ], - )..initialPosition = Vector2(-25.2, 1.5), - AndroidBumper.b( - children: [ - ScoringContactBehavior(points: Points.twentyThousand), - BumperNoiseBehavior(), - ], - )..initialPosition = Vector2(-32.9, -9.3), - AndroidBumper.cow( - children: [ - ScoringContactBehavior(points: Points.twentyThousand), - BumperNoiseBehavior(), - ], - )..initialPosition = Vector2(-20.7, -13), - AndroidSpaceshipBonusBehavior(), ], ); diff --git a/lib/game/components/android_acres/behaviors/android_spaceship_bonus_behavior.dart b/lib/game/components/android_acres/behaviors/android_spaceship_bonus_behavior.dart index da181f9e..3b6f59ec 100644 --- a/lib/game/components/android_acres/behaviors/android_spaceship_bonus_behavior.dart +++ b/lib/game/components/android_acres/behaviors/android_spaceship_bonus_behavior.dart @@ -5,22 +5,21 @@ import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; /// Adds a [GameBonus.androidSpaceship] when [AndroidSpaceship] has a bonus. -class AndroidSpaceshipBonusBehavior extends Component - with ParentIsA, FlameBlocReader { +class AndroidSpaceshipBonusBehavior extends Component { @override - void onMount() { - super.onMount(); - final androidSpaceship = parent.firstChild()!; - - // TODO(alestiago): Refactor subscription management once the following is - // merged: - // https://github.com/flame-engine/flame/pull/1538 - androidSpaceship.bloc.stream.listen((state) { - final listenWhen = state == AndroidSpaceshipState.withBonus; - if (!listenWhen) return; - - bloc.add(const BonusActivated(GameBonus.androidSpaceship)); - androidSpaceship.bloc.onBonusAwarded(); - }); + Future onLoad() async { + await super.onLoad(); + await add( + FlameBlocListener( + listenWhen: (_, state) => state == AndroidSpaceshipState.withBonus, + onNewState: (state) { + readBloc().add( + const BonusActivated(GameBonus.androidSpaceship), + ); + readBloc() + .onBonusAwarded(); + }, + ), + ); } } diff --git a/lib/game/components/backbox/backbox.dart b/lib/game/components/backbox/backbox.dart index b3743df3..dad2c118 100644 --- a/lib/game/components/backbox/backbox.dart +++ b/lib/game/components/backbox/backbox.dart @@ -5,27 +5,45 @@ import 'package:flutter/material.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/game/components/backbox/bloc/backbox_bloc.dart'; import 'package:pinball/game/components/backbox/displays/displays.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball/l10n/l10n.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' hide Assets; +import 'package:pinball_ui/pinball_ui.dart'; +import 'package:platform_helper/platform_helper.dart'; +import 'package:share_repository/share_repository.dart'; /// {@template backbox} /// The [Backbox] of the pinball machine. /// {@endtemplate} -class Backbox extends PositionComponent with ZIndex { +class Backbox extends PositionComponent with ZIndex, HasGameRef { /// {@macro backbox} Backbox({ required LeaderboardRepository leaderboardRepository, - }) : _bloc = BackboxBloc(leaderboardRepository: leaderboardRepository); + required ShareRepository shareRepository, + required List? entries, + }) : _bloc = BackboxBloc( + leaderboardRepository: leaderboardRepository, + initialEntries: entries, + ), + _shareRepository = shareRepository, + _platformHelper = PlatformHelper(); /// {@macro backbox} @visibleForTesting Backbox.test({ required BackboxBloc bloc, - }) : _bloc = bloc; + required ShareRepository shareRepository, + required PlatformHelper platformHelper, + }) : _bloc = bloc, + _shareRepository = shareRepository, + _platformHelper = platformHelper; + final ShareRepository _shareRepository; late final Component _display; final BackboxBloc _bloc; + final PlatformHelper _platformHelper; late StreamSubscription _subscription; @override @@ -34,8 +52,6 @@ class Backbox extends PositionComponent with ZIndex { anchor = Anchor.bottomCenter; zIndex = ZIndexes.backbox; - _bloc.add(LeaderboardRequested()); - await add(_BackboxSpriteComponent()); await add(_display = Component()); _build(_bloc.state); @@ -57,7 +73,12 @@ class Backbox extends PositionComponent with ZIndex { _display.add(LoadingDisplay()); } else if (state is LeaderboardSuccessState) { _display.add(LeaderboardDisplay(entries: state.entries)); + } else if (state is LeaderboardFailureState) { + _display.add(LeaderboardFailureDisplay()); } else if (state is InitialsFormState) { + if (_platformHelper.isMobile) { + gameRef.overlays.add(PinballGame.mobileControlsOverlay); + } _display.add( InitialsInputDisplay( score: state.score, @@ -74,9 +95,42 @@ class Backbox extends PositionComponent with ZIndex { ), ); } else if (state is InitialsSuccessState) { - _display.add(InitialsSubmissionSuccessDisplay()); + gameRef.overlays.remove(PinballGame.mobileControlsOverlay); + + _display.add( + GameOverInfoDisplay( + onShare: () { + _bloc.add(ShareScoreRequested(score: state.score)); + }, + ), + ); + } else if (state is ShareState) { + _display.add( + ShareDisplay( + onShare: (platform) { + final message = readProvider() + .iGotScoreAtPinball(state.score); + final url = _shareRepository.shareText( + value: message, + platform: platform, + ); + openLink(url); + }, + ), + ); } else if (state is InitialsFailureState) { - _display.add(InitialsSubmissionFailureDisplay()); + _display.add( + InitialsSubmissionFailureDisplay( + onDismissed: () { + _bloc.add( + PlayerInitialsRequested( + score: state.score, + character: state.character, + ), + ); + }, + ), + ); } } diff --git a/lib/game/components/backbox/bloc/backbox_bloc.dart b/lib/game/components/backbox/bloc/backbox_bloc.dart index b3952a0c..cfac4c6f 100644 --- a/lib/game/components/backbox/bloc/backbox_bloc.dart +++ b/lib/game/components/backbox/bloc/backbox_bloc.dart @@ -14,10 +14,16 @@ class BackboxBloc extends Bloc { /// {@macro backbox_bloc} BackboxBloc({ required LeaderboardRepository leaderboardRepository, + required List? initialEntries, }) : _leaderboardRepository = leaderboardRepository, - super(LoadingState()) { + super( + initialEntries != null + ? LeaderboardSuccessState(entries: initialEntries) + : LeaderboardFailureState(), + ) { on(_onPlayerInitialsRequested); on(_onPlayerInitialsSubmitted); + on(_onScoreShareRequested); on(_onLeaderboardRequested); } @@ -48,13 +54,31 @@ class BackboxBloc extends Bloc { character: event.character.toType, ), ); - emit(InitialsSuccessState()); + emit( + InitialsSuccessState( + score: event.score, + ), + ); } catch (error, stackTrace) { addError(error, stackTrace); - emit(InitialsFailureState()); + emit( + InitialsFailureState( + score: event.score, + character: event.character, + ), + ); } } + Future _onScoreShareRequested( + ShareScoreRequested event, + Emitter emit, + ) async { + emit( + ShareState(score: event.score), + ); + } + Future _onLeaderboardRequested( LeaderboardRequested event, Emitter emit, diff --git a/lib/game/components/backbox/bloc/backbox_event.dart b/lib/game/components/backbox/bloc/backbox_event.dart index 40ad4bfb..a842d191 100644 --- a/lib/game/components/backbox/bloc/backbox_event.dart +++ b/lib/game/components/backbox/bloc/backbox_event.dart @@ -52,6 +52,22 @@ class PlayerInitialsSubmitted extends BackboxEvent { List get props => [score, initials, character]; } +/// {@template share_score_requested} +/// Event when user requests to share their score. +/// {@endtemplate} +class ShareScoreRequested extends BackboxEvent { + /// {@macro share_score_requested} + const ShareScoreRequested({ + required this.score, + }); + + /// Player's score. + final int score; + + @override + List get props => [score]; +} + /// Event that triggers the fetching of the leaderboard class LeaderboardRequested extends BackboxEvent { @override diff --git a/lib/game/components/backbox/bloc/backbox_state.dart b/lib/game/components/backbox/bloc/backbox_state.dart index 482bb298..d1025a09 100644 --- a/lib/game/components/backbox/bloc/backbox_state.dart +++ b/lib/game/components/backbox/bloc/backbox_state.dart @@ -54,14 +54,51 @@ class InitialsFormState extends BackboxState { List get props => [score, character]; } -/// State when the leaderboard was successfully loaded. +/// {@template initials_success_state} +/// State when the score and initials were successfully submitted. +/// {@endtemplate} class InitialsSuccessState extends BackboxState { + /// {@macro initials_success_state} + const InitialsSuccessState({ + required this.score, + }) : super(); + + /// Player's score. + final int score; + @override - List get props => []; + List get props => [score]; } /// State when the initials submission failed. class InitialsFailureState extends BackboxState { + const InitialsFailureState({ + required this.score, + required this.character, + }); + + /// Player's score. + final int score; + + /// Player's character. + final CharacterTheme character; + @override - List get props => []; + List get props => [score, character]; +} + +/// {@template share_state} +/// State when the user is sharing their score. +/// {@endtemplate} +class ShareState extends BackboxState { + /// {@macro share_state} + const ShareState({ + required this.score, + }) : super(); + + /// Player's score. + final int score; + + @override + List get props => [score]; } diff --git a/lib/game/components/backbox/displays/displays.dart b/lib/game/components/backbox/displays/displays.dart index 4dda7048..f80c0b54 100644 --- a/lib/game/components/backbox/displays/displays.dart +++ b/lib/game/components/backbox/displays/displays.dart @@ -1,5 +1,8 @@ +export 'game_over_info_display.dart'; export 'initials_input_display.dart'; export 'initials_submission_failure_display.dart'; export 'initials_submission_success_display.dart'; export 'leaderboard_display.dart'; +export 'leaderboard_failure_display.dart'; export 'loading_display.dart'; +export 'share_display.dart'; diff --git a/lib/game/components/backbox/displays/game_over_info_display.dart b/lib/game/components/backbox/displays/game_over_info_display.dart new file mode 100644 index 00000000..c7708bde --- /dev/null +++ b/lib/game/components/backbox/displays/game_over_info_display.dart @@ -0,0 +1,311 @@ +import 'dart:async'; + +import 'package:flame/components.dart'; +import 'package:flame/input.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:pinball_ui/pinball_ui.dart'; +import 'package:share_repository/share_repository.dart'; + +/// Signature for the callback called when the user tries to share their score +/// from the [GameOverInfoDisplay]. +typedef OnShareTap = void Function(); + +final _titleTextPaint = TextPaint( + style: const TextStyle( + fontSize: 1.6, + color: PinballColors.white, + fontFamily: PinballFonts.pixeloidSans, + ), +); + +final _titleBoldTextPaint = TextPaint( + style: const TextStyle( + fontSize: 1.4, + color: PinballColors.white, + fontFamily: PinballFonts.pixeloidSans, + fontWeight: FontWeight.bold, + ), +); + +final _linkTextPaint = TextPaint( + style: const TextStyle( + fontSize: 1.7, + color: PinballColors.orange, + fontFamily: PinballFonts.pixeloidSans, + fontWeight: FontWeight.bold, + ), +); + +final _descriptionTextPaint = TextPaint( + style: const TextStyle( + fontSize: 1.6, + color: PinballColors.white, + fontFamily: PinballFonts.pixeloidSans, + ), +); + +/// {@template game_over_info_display} +/// Display with links to share your score or go to the IO webpage. +/// {@endtemplate} +class GameOverInfoDisplay extends Component with HasGameRef { + /// {@macro game_over_info_display} + GameOverInfoDisplay({ + OnShareTap? onShare, + }) : super( + children: [ + _InstructionsComponent( + onShare: onShare, + ), + ], + ); + + @override + Future onLoad() async { + await super.onLoad(); + gameRef.overlays.add(PinballGame.playButtonOverlay); + } +} + +class _InstructionsComponent extends PositionComponent with HasGameRef { + _InstructionsComponent({ + OnShareTap? onShare, + }) : super( + anchor: Anchor.center, + position: Vector2(0, -25), + children: [ + _TitleComponent(), + _LinksComponent( + onShare: onShare, + ), + _DescriptionComponent(), + ], + ); +} + +class _TitleComponent extends PositionComponent with HasGameRef { + _TitleComponent() + : super( + anchor: Anchor.center, + position: Vector2(0, 3), + children: [ + _TitleBackgroundSpriteComponent(), + _ShareScoreTextComponent(), + _ChallengeFriendsTextComponent(), + ], + ); +} + +class _ShareScoreTextComponent extends TextComponent with HasGameRef { + _ShareScoreTextComponent() + : super( + anchor: Anchor.center, + position: Vector2(0, -1.5), + textRenderer: _titleTextPaint, + ); + + @override + Future onLoad() async { + await super.onLoad(); + text = readProvider().shareYourScore; + } +} + +class _ChallengeFriendsTextComponent extends TextComponent with HasGameRef { + _ChallengeFriendsTextComponent() + : super( + anchor: Anchor.center, + position: Vector2(0, 1.5), + textRenderer: _titleBoldTextPaint, + ); + + @override + Future onLoad() async { + await super.onLoad(); + text = readProvider().andChallengeYourFriends; + } +} + +class _TitleBackgroundSpriteComponent extends SpriteComponent with HasGameRef { + _TitleBackgroundSpriteComponent() + : super( + anchor: Anchor.center, + position: Vector2.zero(), + ); + + @override + Future onLoad() async { + await super.onLoad(); + final sprite = Sprite( + gameRef.images + .fromCache(Assets.images.backbox.displayTitleDecoration.keyName), + ); + this.sprite = sprite; + size = sprite.originalSize / 22; + } +} + +class _LinksComponent extends PositionComponent with HasGameRef { + _LinksComponent({ + OnShareTap? onShare, + }) : super( + anchor: Anchor.center, + position: Vector2(0, 9.2), + children: [ + ShareLinkComponent(onTap: onShare), + GoogleIOLinkComponent(), + ], + ); +} + +/// {@template share_link_component} +/// Link button to navigate to sharing score display. +/// {@endtemplate} +class ShareLinkComponent extends TextComponent with HasGameRef, Tappable { + /// {@macro share_link_component} + ShareLinkComponent({ + OnShareTap? onTap, + }) : _onTap = onTap, + super( + anchor: Anchor.center, + position: Vector2(-7, 0), + textRenderer: _linkTextPaint, + ); + + final OnShareTap? _onTap; + + @override + bool onTapDown(TapDownInfo info) { + _onTap?.call(); + return true; + } + + @override + Future onLoad() async { + await super.onLoad(); + await add( + RectangleComponent( + size: Vector2(6.4, 0.2), + paint: Paint()..color = PinballColors.orange, + anchor: Anchor.center, + position: Vector2(3.2, 2.3), + ), + ); + + text = readProvider().share; + } +} + +/// {@template google_io_link_component} +/// Link button to navigate to Google I/O site. +/// {@endtemplate} +class GoogleIOLinkComponent extends TextComponent with HasGameRef, Tappable { + /// {@macro google_io_link_component} + GoogleIOLinkComponent() + : super( + anchor: Anchor.center, + position: Vector2(6, 0), + textRenderer: _linkTextPaint, + ); + + @override + bool onTapDown(TapDownInfo info) { + openLink(ShareRepository.googleIOEvent); + return true; + } + + @override + Future onLoad() async { + await super.onLoad(); + await add( + RectangleComponent( + size: Vector2(10.2, 0.2), + paint: Paint()..color = PinballColors.orange, + anchor: Anchor.center, + position: Vector2(5.1, 2.3), + ), + ); + + text = readProvider().gotoIO; + } +} + +class _DescriptionComponent extends PositionComponent with HasGameRef { + _DescriptionComponent() + : super( + anchor: Anchor.center, + position: Vector2(0, 13), + children: [ + _LearnMoreTextComponent(), + _FirebaseTextComponent(), + OpenSourceTextComponent(), + ], + ); +} + +class _LearnMoreTextComponent extends TextComponent with HasGameRef { + _LearnMoreTextComponent() + : super( + anchor: Anchor.center, + position: Vector2.zero(), + textRenderer: _descriptionTextPaint, + ); + + @override + Future onLoad() async { + await super.onLoad(); + text = readProvider().learnMore; + } +} + +class _FirebaseTextComponent extends TextComponent with HasGameRef { + _FirebaseTextComponent() + : super( + anchor: Anchor.center, + position: Vector2(-8.5, 2.5), + textRenderer: _descriptionTextPaint, + ); + + @override + Future onLoad() async { + await super.onLoad(); + text = readProvider().firebaseOr; + } +} + +/// {@template open_source_link_component} +/// Link text to navigate to Open Source site. +/// {@endtemplate} +@visibleForTesting +class OpenSourceTextComponent extends TextComponent with HasGameRef, Tappable { + /// {@macro open_source_link_component} + OpenSourceTextComponent() + : super( + anchor: Anchor.center, + position: Vector2(13.5, 2.5), + textRenderer: _descriptionTextPaint, + ); + + @override + bool onTapDown(TapDownInfo info) { + openLink(ShareRepository.openSourceCode); + return true; + } + + @override + Future onLoad() async { + await super.onLoad(); + await add( + RectangleComponent( + size: Vector2(16, 0.2), + paint: Paint()..color = PinballColors.white, + anchor: Anchor.center, + position: Vector2(8, 2.3), + ), + ); + text = readProvider().openSourceCode; + } +} diff --git a/lib/game/components/backbox/displays/initials_input_display.dart b/lib/game/components/backbox/displays/initials_input_display.dart index 244a3e5b..54780c2d 100644 --- a/lib/game/components/backbox/displays/initials_input_display.dart +++ b/lib/game/components/backbox/displays/initials_input_display.dart @@ -32,7 +32,6 @@ final _subtitleTextPaint = TextPaint( /// {@template initials_input_display} /// Display that handles the user input on the game over view. /// {@endtemplate} -// TODO(allisonryan0002): add mobile input buttons. class InitialsInputDisplay extends Component with HasGameRef { /// {@macro initials_input_display} InitialsInputDisplay({ @@ -78,7 +77,7 @@ class InitialsInputDisplay extends Component with HasGameRef { ); } - /// Returns the current inputed initials + /// Returns the current entered initials String get initials => children .whereType() .map((prompt) => prompt.char) diff --git a/lib/game/components/backbox/displays/initials_submission_failure_display.dart b/lib/game/components/backbox/displays/initials_submission_failure_display.dart index 4cc5a9f5..a9bd84c5 100644 --- a/lib/game/components/backbox/displays/initials_submission_failure_display.dart +++ b/lib/game/components/backbox/displays/initials_submission_failure_display.dart @@ -1,27 +1,47 @@ import 'package:flame/components.dart'; import 'package:flutter/material.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/l10n/l10n.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_ui/pinball_ui.dart'; final _bodyTextPaint = TextPaint( style: const TextStyle( - fontSize: 3, + fontSize: 1.8, color: PinballColors.white, fontFamily: PinballFonts.pixeloidSans, + fontWeight: FontWeight.w400, ), ); /// {@template initials_submission_failure_display} /// [Backbox] display for when a failure occurs during initials submission. /// {@endtemplate} -class InitialsSubmissionFailureDisplay extends TextComponent { +class InitialsSubmissionFailureDisplay extends Component { + /// {@macro initials_submission_failure_display} + InitialsSubmissionFailureDisplay({ + required this.onDismissed, + }); + + final VoidCallback onDismissed; + @override Future onLoad() async { - await super.onLoad(); - position = Vector2(0, -10); - anchor = Anchor.center; - text = 'Failure!'; - textRenderer = _bodyTextPaint; + final l10n = readProvider(); + + await addAll([ + ErrorComponent.bold( + label: l10n.initialsErrorTitle, + position: Vector2(0, -20), + ), + TextComponent( + text: l10n.initialsErrorMessage, + anchor: Anchor.center, + position: Vector2(0, -12), + textRenderer: _bodyTextPaint, + ), + TimerComponent(period: 4, onTick: onDismissed), + ]); } } diff --git a/lib/game/components/backbox/displays/leaderboard_failure_display.dart b/lib/game/components/backbox/displays/leaderboard_failure_display.dart new file mode 100644 index 00000000..a519f9e2 --- /dev/null +++ b/lib/game/components/backbox/displays/leaderboard_failure_display.dart @@ -0,0 +1,23 @@ +import 'package:flame/components.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// {@template leaderboard_failure_display} +/// Display showing an error message when the leaderboard couldn't be loaded +/// {@endtemplate} +class LeaderboardFailureDisplay extends Component { + /// {@macro leaderboard_failure_display} + LeaderboardFailureDisplay(); + + @override + Future onLoad() async { + final l10n = readProvider(); + await add( + ErrorComponent( + label: l10n.leaderboardErrorMessage, + position: Vector2(0, -18), + ), + ); + } +} diff --git a/lib/game/components/backbox/displays/share_display.dart b/lib/game/components/backbox/displays/share_display.dart new file mode 100644 index 00000000..ebaaac7e --- /dev/null +++ b/lib/game/components/backbox/displays/share_display.dart @@ -0,0 +1,189 @@ +import 'dart:async'; + +import 'package:flame/components.dart'; +import 'package:flame/input.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:pinball_ui/pinball_ui.dart'; +import 'package:share_repository/share_repository.dart'; + +/// Signature for the callback called when the user tries to share their score +/// on the [ShareDisplay]. +typedef OnSocialShareTap = void Function(SharePlatform); + +final _descriptionTextPaint = TextPaint( + style: const TextStyle( + fontSize: 1.6, + color: PinballColors.white, + fontFamily: PinballFonts.pixeloidSans, + ), +); + +/// {@template share_display} +/// Display that allows users to share their score to social networks. +/// {@endtemplate} +class ShareDisplay extends Component with HasGameRef { + /// {@macro share_display} + ShareDisplay({ + OnSocialShareTap? onShare, + }) : super( + children: [ + _ShareInstructionsComponent( + onShare: onShare, + ), + ], + ); +} + +class _ShareInstructionsComponent extends PositionComponent with HasGameRef { + _ShareInstructionsComponent({ + OnSocialShareTap? onShare, + }) : super( + anchor: Anchor.center, + position: Vector2(0, -25), + children: [ + _DescriptionComponent(), + _SocialNetworksComponent( + onShare: onShare, + ), + ], + ); +} + +class _DescriptionComponent extends PositionComponent with HasGameRef { + _DescriptionComponent() + : super( + anchor: Anchor.center, + position: Vector2.zero(), + children: [ + _LetEveryoneTextComponent(), + _SharingYourScoreTextComponent(), + _SocialMediaTextComponent(), + ], + ); +} + +class _LetEveryoneTextComponent extends TextComponent with HasGameRef { + _LetEveryoneTextComponent() + : super( + anchor: Anchor.center, + position: Vector2.zero(), + textRenderer: _descriptionTextPaint, + ); + + @override + Future onLoad() async { + await super.onLoad(); + text = readProvider().letEveryone; + } +} + +class _SharingYourScoreTextComponent extends TextComponent with HasGameRef { + _SharingYourScoreTextComponent() + : super( + anchor: Anchor.center, + position: Vector2(0, 2.5), + textRenderer: _descriptionTextPaint, + ); + + @override + Future onLoad() async { + await super.onLoad(); + text = readProvider().bySharingYourScore; + } +} + +class _SocialMediaTextComponent extends TextComponent with HasGameRef { + _SocialMediaTextComponent() + : super( + anchor: Anchor.center, + position: Vector2(0, 5), + textRenderer: _descriptionTextPaint, + ); + + @override + Future onLoad() async { + await super.onLoad(); + text = readProvider().socialMediaAccount; + } +} + +class _SocialNetworksComponent extends PositionComponent with HasGameRef { + _SocialNetworksComponent({ + OnSocialShareTap? onShare, + }) : super( + anchor: Anchor.center, + position: Vector2(0, 12), + children: [ + FacebookButtonComponent(onTap: onShare), + TwitterButtonComponent(onTap: onShare), + ], + ); +} + +/// {@template facebook_button_component} +/// Button for sharing on Facebook. +/// {@endtemplate} +class FacebookButtonComponent extends SpriteComponent + with HasGameRef, Tappable { + /// {@macro facebook_button_component} + FacebookButtonComponent({ + OnSocialShareTap? onTap, + }) : _onTap = onTap, + super( + anchor: Anchor.center, + position: Vector2(-5, 0), + ); + + final OnSocialShareTap? _onTap; + + @override + bool onTapDown(TapDownInfo info) { + _onTap?.call(SharePlatform.facebook); + return true; + } + + @override + Future onLoad() async { + await super.onLoad(); + final sprite = Sprite( + gameRef.images.fromCache(Assets.images.backbox.button.facebook.keyName), + ); + this.sprite = sprite; + size = sprite.originalSize / 25; + } +} + +/// {@template twitter_button_component} +/// Button for sharing on Twitter. +/// {@endtemplate} +class TwitterButtonComponent extends SpriteComponent with HasGameRef, Tappable { + /// {@macro twitter_button_component} + TwitterButtonComponent({ + OnSocialShareTap? onTap, + }) : _onTap = onTap, + super( + anchor: Anchor.center, + position: Vector2(5, 0), + ); + + final OnSocialShareTap? _onTap; + + @override + bool onTapDown(TapDownInfo info) { + _onTap?.call(SharePlatform.twitter); + return true; + } + + @override + Future onLoad() async { + await super.onLoad(); + final sprite = Sprite( + gameRef.images.fromCache(Assets.images.backbox.button.twitter.keyName), + ); + this.sprite = sprite; + size = sprite.originalSize / 25; + } +} diff --git a/lib/game/components/bottom_group.dart b/lib/game/components/bottom_group.dart index 13bef589..cfa7e434 100644 --- a/lib/game/components/bottom_group.dart +++ b/lib/game/components/bottom_group.dart @@ -1,6 +1,5 @@ import 'package:flame/components.dart'; import 'package:pinball/game/behaviors/behaviors.dart'; -import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; @@ -9,7 +8,6 @@ import 'package:pinball_flame/pinball_flame.dart'; /// /// The [BottomGroup] consists of [Flipper]s, [Baseboard]s and [Kicker]s. /// {@endtemplate} -// TODO(allisonryan0002): Consider renaming. class BottomGroup extends Component with ZIndex { /// {@macro bottom_group} BottomGroup() @@ -41,7 +39,7 @@ class _BottomGroupSide extends Component { final direction = _side.direction; final centerXAdjustment = _side.isLeft ? -0.45 : -6.8; - final flipper = ControlledFlipper( + final flipper = Flipper( side: _side, )..initialPosition = Vector2((11.6 * direction) + centerXAdjustment, 43.6); final baseboard = Baseboard(side: _side) @@ -54,6 +52,7 @@ class _BottomGroupSide extends Component { children: [ ScoringContactBehavior(points: Points.fiveThousand) ..applyTo(['bouncy_edge']), + KickerNoiseBehavior()..applyTo(['bouncy_edge']), ], )..initialPosition = Vector2( (22.44 * direction) + centerXAdjustment, diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 08dc5cb0..324f379a 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -1,7 +1,6 @@ export 'android_acres/android_acres.dart'; export 'backbox/backbox.dart'; export 'bottom_group.dart'; -export 'controlled_flipper.dart'; export 'controlled_plunger.dart'; export 'dino_desert/dino_desert.dart'; export 'drain/drain.dart'; diff --git a/lib/game/components/controlled_plunger.dart b/lib/game/components/controlled_plunger.dart index c8cb90fb..f709de66 100644 --- a/lib/game/components/controlled_plunger.dart +++ b/lib/game/components/controlled_plunger.dart @@ -30,7 +30,7 @@ class PlungerNoiseBehavior extends Component { @override Future onLoad() async { await super.onLoad(); - readProvider().play(PinballAudio.launcher); + readProvider().play(PinballAudio.launcher); } @override diff --git a/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart b/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart index f1e4f53d..60cd1857 100644 --- a/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart +++ b/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart @@ -11,10 +11,6 @@ class ChromeDinoBonusBehavior extends Component void onMount() { super.onMount(); final chromeDino = parent.firstChild()!; - - // TODO(alestiago): Refactor subscription management once the following is - // merged: - // https://github.com/flame-engine/flame/pull/1538 chromeDino.bloc.stream.listen((state) { final listenWhen = state.status == ChromeDinoStatus.chomping; if (!listenWhen) return; diff --git a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart index 55902eb7..3c4ef02a 100644 --- a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart +++ b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart @@ -1,15 +1,15 @@ import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; /// Bonus obtained at the [FlutterForest]. /// -/// When all [DashNestBumper]s are hit at least once three times, the [Signpost] +/// When all [DashBumper]s are hit at least once three times, the [Signpost] /// progresses. When the [Signpost] fully progresses, the [GameBonus.dashNest] -/// is awarded, and the [DashNestBumper.main] releases a new [Ball]. +/// is awarded, and the [DashBumper.main] releases a new [Ball]. class FlutterForestBonusBehavior extends Component with ParentIsA, @@ -19,18 +19,14 @@ class FlutterForestBonusBehavior extends Component void onMount() { super.onMount(); - final bumpers = parent.children.whereType(); + final bumpers = parent.children.whereType(); final signpost = parent.firstChild()!; final animatronic = parent.firstChild()!; - final canvas = gameRef.descendants().whereType().single; for (final bumper in bumpers) { - // TODO(alestiago): Refactor subscription management once the following is - // merged: - // https://github.com/flame-engine/flame/pull/1538 bumper.bloc.stream.listen((state) { final activatedAllBumpers = bumpers.every( - (bumper) => bumper.bloc.state == DashNestBumperState.active, + (bumper) => bumper.bloc.state == DashBumperState.active, ); if (activatedAllBumpers) { @@ -41,14 +37,7 @@ class FlutterForestBonusBehavior extends Component if (signpost.bloc.isFullyProgressed()) { bloc.add(const BonusActivated(GameBonus.dashNest)); - final characterTheme = readProvider(); - canvas.add( - Ball( - assetPath: characterTheme.ball.keyName, - ) - ..initialPosition = Vector2(29.2, -24.5) - ..zIndex = ZIndexes.ballOnBoard, - ); + add(BonusBallSpawningBehavior()); animatronic.playing = true; signpost.bloc.onProgressed(); } diff --git a/lib/game/components/flutter_forest/flutter_forest.dart b/lib/game/components/flutter_forest/flutter_forest.dart index 1cc055ae..39783bb1 100644 --- a/lib/game/components/flutter_forest/flutter_forest.dart +++ b/lib/game/components/flutter_forest/flutter_forest.dart @@ -9,7 +9,7 @@ import 'package:pinball_flame/pinball_flame.dart'; /// {@template flutter_forest} /// Area positioned at the top right of the board where the [Ball] can bounce -/// off [DashNestBumper]s. +/// off [DashBumper]s. /// {@endtemplate} class FlutterForest extends Component with ZIndex { /// {@macro flutter_forest} @@ -22,19 +22,19 @@ class FlutterForest extends Component with ZIndex { BumperNoiseBehavior(), ], )..initialPosition = Vector2(7.95, -58.35), - DashNestBumper.main( + DashBumper.main( children: [ ScoringContactBehavior(points: Points.twoHundredThousand), BumperNoiseBehavior(), ], )..initialPosition = Vector2(18.55, -59.35), - DashNestBumper.a( + DashBumper.a( children: [ ScoringContactBehavior(points: Points.twentyThousand), BumperNoiseBehavior(), ], )..initialPosition = Vector2(8.95, -51.95), - DashNestBumper.b( + DashBumper.b( children: [ ScoringContactBehavior(points: Points.twentyThousand), BumperNoiseBehavior(), diff --git a/lib/game/components/game_bloc_status_listener.dart b/lib/game/components/game_bloc_status_listener.dart index 6e11f3d6..1a5a06df 100644 --- a/lib/game/components/game_bloc_status_listener.dart +++ b/lib/game/components/game_bloc_status_listener.dart @@ -1,9 +1,10 @@ import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; /// Listens to the [GameBloc] and updates the game accordingly. class GameBlocStatusListener extends Component @@ -19,16 +20,39 @@ class GameBlocStatusListener extends Component case GameStatus.waiting: break; case GameStatus.playing: - readProvider().play(PinballAudio.backgroundMusic); + readProvider().play(PinballAudio.backgroundMusic); + gameRef + .descendants() + .whereType() + .forEach(_addFlipperKeyControls); + gameRef.overlays.remove(PinballGame.playButtonOverlay); break; case GameStatus.gameOver: - readProvider().play(PinballAudio.gameOverVoiceOver); + readProvider().play(PinballAudio.gameOverVoiceOver); gameRef.descendants().whereType().first.requestInitials( score: state.displayScore, - character: readProvider(), + character: readBloc() + .state + .characterTheme, ); + + gameRef + .descendants() + .whereType() + .forEach(_removeFlipperKeyControls); break; } } + + void _addFlipperKeyControls(Flipper flipper) { + flipper + ..add(FlipperKeyControllingBehavior()) + ..moveDown(); + } + + void _removeFlipperKeyControls(Flipper flipper) => flipper + .descendants() + .whereType() + .forEach(flipper.remove); } diff --git a/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart b/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart index 586b8547..c1c14ed5 100644 --- a/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart +++ b/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart @@ -13,9 +13,6 @@ class GoogleWordBonusBehavior extends Component final googleLetters = parent.children.whereType(); for (final letter in googleLetters) { - // TODO(alestiago): Refactor subscription management once the following is - // merged: - // https://github.com/flame-engine/flame/pull/1538 letter.bloc.stream.listen((_) { final achievedBonus = googleLetters .every((letter) => letter.bloc.state == GoogleLetterState.lit); diff --git a/lib/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior.dart b/lib/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior.dart index 15deab29..d1b8898e 100644 --- a/lib/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior.dart +++ b/lib/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior.dart @@ -13,10 +13,6 @@ class SparkyComputerBonusBehavior extends Component super.onMount(); final sparkyComputer = parent.firstChild()!; final animatronic = parent.firstChild()!; - - // TODO(alestiago): Refactor subscription management once the following is - // merged: - // https://github.com/flame-engine/flame/pull/1538 sparkyComputer.bloc.stream.listen((state) async { final listenWhen = state == SparkyComputerState.withBall; if (!listenWhen) return; diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index ac324417..2f386695 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -1,3 +1,4 @@ +import 'package:flame/extensions.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart' as components; import 'package:pinball_theme/pinball_theme.dart' hide Assets; @@ -5,7 +6,7 @@ import 'package:pinball_theme/pinball_theme.dart' hide Assets; /// Add methods to help loading and caching game assets. extension PinballGameAssetsX on PinballGame { /// Returns a list of assets to be loaded - List preLoadAssets() { + List> preLoadAssets() { const dashTheme = DashTheme(); const sparkyTheme = SparkyTheme(); const androidTheme = AndroidTheme(); @@ -100,6 +101,11 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.sparky.bumper.c.dimmed.keyName), images.load(components.Assets.images.backbox.marquee.keyName), images.load(components.Assets.images.backbox.displayDivider.keyName), + images.load(components.Assets.images.backbox.button.facebook.keyName), + images.load(components.Assets.images.backbox.button.twitter.keyName), + images.load( + components.Assets.images.backbox.displayTitleDecoration.keyName, + ), images.load(components.Assets.images.googleWord.letter1.lit.keyName), images.load(components.Assets.images.googleWord.letter1.dimmed.keyName), images.load(components.Assets.images.googleWord.letter2.lit.keyName), @@ -135,13 +141,17 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.skillShot.pin.keyName), images.load(components.Assets.images.skillShot.lit.keyName), images.load(components.Assets.images.skillShot.dimmed.keyName), - images.load(dashTheme.leaderboardIcon.keyName), - images.load(sparkyTheme.leaderboardIcon.keyName), images.load(androidTheme.leaderboardIcon.keyName), - images.load(dinoTheme.leaderboardIcon.keyName), + images.load(androidTheme.background.keyName), images.load(androidTheme.ball.keyName), + images.load(dashTheme.leaderboardIcon.keyName), + images.load(dashTheme.background.keyName), images.load(dashTheme.ball.keyName), + images.load(dinoTheme.leaderboardIcon.keyName), + images.load(dinoTheme.background.keyName), images.load(dinoTheme.ball.keyName), + images.load(sparkyTheme.leaderboardIcon.keyName), + images.load(sparkyTheme.background.keyName), images.load(sparkyTheme.ball.keyName), ]; } diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 6818f566..8a213d31 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -1,4 +1,3 @@ -// ignore_for_file: public_member_api_docs import 'dart:async'; import 'package:flame/components.dart'; @@ -11,23 +10,25 @@ import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; +import 'package:share_repository/share_repository.dart'; class PinballGame extends PinballForge2DGame - with HasKeyboardHandlerComponents, MultiTouchTapDetector { + with HasKeyboardHandlerComponents, MultiTouchTapDetector, HasTappables { PinballGame({ - required CharacterTheme characterTheme, + required CharacterThemeCubit characterThemeBloc, required this.leaderboardRepository, + required this.shareRepository, required GameBloc gameBloc, required AppLocalizations l10n, - required PinballPlayer player, + required PinballAudioPlayer audioPlayer, }) : focusNode = FocusNode(), _gameBloc = gameBloc, - _player = player, - _characterTheme = characterTheme, + _audioPlayer = audioPlayer, + _characterThemeBloc = characterThemeBloc, _l10n = l10n, super( gravity: Vector2(0, 30), @@ -38,38 +39,63 @@ class PinballGame extends PinballForge2DGame /// Identifier of the play button overlay static const playButtonOverlay = 'play_button'; + /// Identifier of the mobile controls overlay + static const mobileControlsOverlay = 'mobile_controls'; + @override Color backgroundColor() => Colors.transparent; final FocusNode focusNode; - final CharacterTheme _characterTheme; + final CharacterThemeCubit _characterThemeBloc; - final PinballPlayer _player; + final PinballAudioPlayer _audioPlayer; final LeaderboardRepository leaderboardRepository; + final ShareRepository shareRepository; + final AppLocalizations _l10n; final GameBloc _gameBloc; + List? _entries; + + Future preFetchLeaderboard() async { + try { + _entries = await leaderboardRepository.fetchTop10Leaderboard(); + } catch (_) { + // An initial null leaderboard means that we couldn't fetch + // the entries for the [Backbox] and it will show the relevant display. + _entries = null; + } + } + @override Future onLoad() async { await add( - FlameBlocProvider.value( - value: _gameBloc, + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value( + value: _gameBloc, + ), + FlameBlocProvider.value( + value: _characterThemeBloc, + ), + ], children: [ MultiFlameProvider( providers: [ - FlameProvider.value(_player), - FlameProvider.value(_characterTheme), + FlameProvider.value(_audioPlayer), FlameProvider.value(leaderboardRepository), + FlameProvider.value(shareRepository), FlameProvider.value(_l10n), ], children: [ BonusNoiseBehavior(), GameBlocStatusListener(), BallSpawningBehavior(), + CharacterSelectionBehavior(), CameraFocusingBehavior(), CanvasComponent( onSpritePainted: (paint) { @@ -80,9 +106,14 @@ class PinballGame extends PinballForge2DGame children: [ ZCanvasComponent( children: [ + ArcadeBackground(), BoardBackgroundSpriteComponent(), Boundaries(), - Backbox(leaderboardRepository: leaderboardRepository), + Backbox( + leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, + entries: _entries, + ), GoogleWord(position: Vector2(-4.45, 1.8)), Multipliers(), Multiballs(), @@ -119,7 +150,7 @@ class PinballGame extends PinballForge2DGame final rocket = descendants().whereType().first; final bounds = rocket.topLeftPosition & rocket.size; - // NOTE(wolfen): As long as Flame does not have https://github.com/flame-engine/flame/issues/1586 we need to check it at the highest level manually. + // NOTE: As long as Flame does not have https://github.com/flame-engine/flame/issues/1586 we need to check it at the highest level manually. if (bounds.contains(info.eventPosition.game.toOffset())) { descendants().whereType().single.pullFor(2); } else { @@ -161,15 +192,17 @@ class PinballGame extends PinballForge2DGame class DebugPinballGame extends PinballGame with FPSCounter, PanDetector { DebugPinballGame({ - required CharacterTheme characterTheme, + required CharacterThemeCubit characterThemeBloc, required LeaderboardRepository leaderboardRepository, + required ShareRepository shareRepository, required AppLocalizations l10n, - required PinballPlayer player, + required PinballAudioPlayer audioPlayer, required GameBloc gameBloc, }) : super( - characterTheme: characterTheme, - player: player, + characterThemeBloc: characterThemeBloc, + audioPlayer: audioPlayer, leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, l10n: l10n, gameBloc: gameBloc, ); @@ -246,7 +279,6 @@ class PreviewLine extends PositionComponent with HasGameRef { } } -// TODO(wolfenrain): investigate this CI failure. class _DebugInformation extends Component with HasGameRef { @override PositionType get positionType => PositionType.widget; diff --git a/lib/game/view/pinball_game_page.dart b/lib/game/view/pinball_game_page.dart index 1005c728..a36754d8 100644 --- a/lib/game/view/pinball_game_page.dart +++ b/lib/game/view/pinball_game_page.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame/game.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -7,13 +5,13 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/assets_manager/assets_manager.dart'; import 'package:pinball/game/game.dart'; -import 'package:pinball/gen/gen.dart'; import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/more_information/more_information.dart'; import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/start_game.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_ui/pinball_ui.dart'; +import 'package:share_repository/share_repository.dart'; class PinballGamePage extends StatelessWidget { const PinballGamePage({ @@ -23,80 +21,61 @@ class PinballGamePage extends StatelessWidget { final bool isDebugMode; - static Route route({bool isDebugMode = kDebugMode}) { - return MaterialPageRoute( - builder: (_) => PinballGamePage(isDebugMode: isDebugMode), - ); - } - @override Widget build(BuildContext context) { - final characterTheme = - context.read().state.characterTheme; - final player = context.read(); + final characterThemeBloc = context.read(); + final audioPlayer = context.read(); final leaderboardRepository = context.read(); + final shareRepository = context.read(); final gameBloc = context.read(); final game = isDebugMode ? DebugPinballGame( - characterTheme: characterTheme, - player: player, + characterThemeBloc: characterThemeBloc, + audioPlayer: audioPlayer, leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, l10n: context.l10n, gameBloc: gameBloc, ) : PinballGame( - characterTheme: characterTheme, - player: player, + characterThemeBloc: characterThemeBloc, + audioPlayer: audioPlayer, leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, l10n: context.l10n, gameBloc: gameBloc, ); - final loadables = [ - ...game.preLoadAssets(), - ...player.load(), - ...BonusAnimation.loadAssets(), - ...SelectedCharacter.loadAssets(), - ]; - - return BlocProvider( - create: (_) => AssetsManagerCubit(loadables)..load(), - child: PinballGameView(game: game), + return Scaffold( + backgroundColor: PinballColors.black, + body: BlocProvider( + create: (_) => AssetsManagerCubit(game, audioPlayer)..load(), + child: PinballGameView(game), + ), ); } } class PinballGameView extends StatelessWidget { - const PinballGameView({ - Key? key, - required this.game, - }) : super(key: key); + const PinballGameView(this.game, {Key? key}) : super(key: key); final PinballGame game; @override Widget build(BuildContext context) { - final isLoading = context.select( - (AssetsManagerCubit bloc) => bloc.state.progress != 1, - ); - return Container( - decoration: const CrtBackground(), - child: Scaffold( - backgroundColor: PinballColors.transparent, - body: isLoading + return BlocBuilder( + builder: (context, state) { + return state.isLoading ? const AssetsLoadingPage() - : PinballGameLoadedView(game: game), - ), + : PinballGameLoadedView(game); + }, ); } } @visibleForTesting class PinballGameLoadedView extends StatelessWidget { - const PinballGameLoadedView({ - Key? key, - required this.game, - }) : super(key: key); + const PinballGameLoadedView(this.game, {Key? key}) : super(key: key); final PinballGame game; @@ -125,6 +104,14 @@ class PinballGameLoadedView extends StatelessWidget { child: PlayButtonOverlay(), ); }, + PinballGame.mobileControlsOverlay: (context, game) { + return Positioned( + bottom: 0, + left: 0, + right: 0, + child: MobileControls(game: game), + ); + }, }, ), ), @@ -179,7 +166,7 @@ class _PositionedInfoIcon extends StatelessWidget { visible: state.status.isGameOver, child: IconButton( iconSize: 50, - icon: Assets.images.linkBox.infoIcon.image(), + icon: const Icon(Icons.info, color: PinballColors.white), onPressed: () => showMoreInformationDialog(context), ), ); diff --git a/lib/game/view/widgets/mobile_controls.dart b/lib/game/view/widgets/mobile_controls.dart new file mode 100644 index 00000000..c5761eb6 --- /dev/null +++ b/lib/game/view/widgets/mobile_controls.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +/// {@template mobile_controls} +/// Widget with the controls used to enable the user initials input on mobile. +/// {@endtemplate} +class MobileControls extends StatelessWidget { + /// {@macro mobile_controls} + const MobileControls({ + Key? key, + required this.game, + }) : super(key: key); + + /// Game instance + final PinballGame game; + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + MobileDpad( + onTapUp: () => game.triggerVirtualKeyUp(LogicalKeyboardKey.arrowUp), + onTapDown: () => game.triggerVirtualKeyUp( + LogicalKeyboardKey.arrowDown, + ), + onTapLeft: () => game.triggerVirtualKeyUp( + LogicalKeyboardKey.arrowLeft, + ), + onTapRight: () => game.triggerVirtualKeyUp( + LogicalKeyboardKey.arrowRight, + ), + ), + PinballButton( + text: l10n.enter, + onTap: () => game.triggerVirtualKeyUp(LogicalKeyboardKey.enter), + ), + ], + ); + } +} diff --git a/lib/game/view/widgets/mobile_dpad.dart b/lib/game/view/widgets/mobile_dpad.dart new file mode 100644 index 00000000..abad496b --- /dev/null +++ b/lib/game/view/widgets/mobile_dpad.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +/// {@template mobile_dpad} +/// Widget rendering 4 directional input arrows. +/// {@endtemplate} +class MobileDpad extends StatelessWidget { + /// {@template mobile_dpad} + const MobileDpad({ + Key? key, + required this.onTapUp, + required this.onTapDown, + required this.onTapLeft, + required this.onTapRight, + }) : super(key: key); + + static const _size = 180.0; + + /// Called when dpad up is pressed + final VoidCallback onTapUp; + + /// Called when dpad down is pressed + final VoidCallback onTapDown; + + /// Called when dpad left is pressed + final VoidCallback onTapLeft; + + /// Called when dpad right is pressed + final VoidCallback onTapRight; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: _size, + height: _size, + child: Column( + children: [ + Row( + children: [ + const Spacer(), + PinballDpadButton( + direction: PinballDpadDirection.up, + onTap: onTapUp, + ), + const Spacer(), + ], + ), + Row( + children: [ + PinballDpadButton( + direction: PinballDpadDirection.left, + onTap: onTapLeft, + ), + const Spacer(), + PinballDpadButton( + direction: PinballDpadDirection.right, + onTap: onTapRight, + ), + ], + ), + Row( + children: [ + const Spacer(), + PinballDpadButton( + direction: PinballDpadDirection.down, + onTap: onTapDown, + ), + const Spacer(), + ], + ), + ], + ), + ); + } +} diff --git a/lib/game/view/widgets/replay_button_overlay.dart b/lib/game/view/widgets/replay_button_overlay.dart new file mode 100644 index 00000000..c0b2a67d --- /dev/null +++ b/lib/game/view/widgets/replay_button_overlay.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/start_game/start_game.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +/// {@template replay_button_overlay} +/// [Widget] that renders the button responsible for restarting the game. +/// {@endtemplate} +class ReplayButtonOverlay extends StatelessWidget { + /// {@macro replay_button_overlay} + const ReplayButtonOverlay({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return PinballButton( + text: l10n.replay, + onTap: () { + context.read().add(const ReplayTapped()); + }, + ); + } +} diff --git a/lib/game/view/widgets/widgets.dart b/lib/game/view/widgets/widgets.dart index 5d1fccf8..0093cad2 100644 --- a/lib/game/view/widgets/widgets.dart +++ b/lib/game/view/widgets/widgets.dart @@ -1,5 +1,8 @@ export 'bonus_animation.dart'; export 'game_hud.dart'; +export 'mobile_controls.dart'; +export 'mobile_dpad.dart'; export 'play_button_overlay.dart'; +export 'replay_button_overlay.dart'; export 'round_count_display.dart'; export 'score_view.dart'; diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index 33d2bbd1..8d81e5c6 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -14,7 +14,8 @@ class $AssetsImagesGen { const $AssetsImagesBonusAnimationGen(); $AssetsImagesComponentsGen get components => const $AssetsImagesComponentsGen(); - $AssetsImagesLinkBoxGen get linkBox => const $AssetsImagesLinkBoxGen(); + $AssetsImagesLoadingGameGen get loadingGame => + const $AssetsImagesLoadingGameGen(); $AssetsImagesScoreGen get score => const $AssetsImagesScoreGen(); } @@ -54,12 +55,12 @@ class $AssetsImagesComponentsGen { const AssetGenImage('assets/images/components/space.png'); } -class $AssetsImagesLinkBoxGen { - const $AssetsImagesLinkBoxGen(); +class $AssetsImagesLoadingGameGen { + const $AssetsImagesLoadingGameGen(); - /// File path: assets/images/link_box/info_icon.png - AssetGenImage get infoIcon => - const AssetGenImage('assets/images/link_box/info_icon.png'); + /// File path: assets/images/loading_game/io_pinball.png + AssetGenImage get ioPinball => + const AssetGenImage('assets/images/loading_game/io_pinball.png'); } class $AssetsImagesScoreGen { diff --git a/lib/how_to_play/widgets/how_to_play_dialog.dart b/lib/how_to_play/widgets/how_to_play_dialog.dart index 1fd26837..8a709605 100644 --- a/lib/how_to_play/widgets/how_to_play_dialog.dart +++ b/lib/how_to_play/widgets/how_to_play_dialog.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:async'; import 'package:flutter/material.dart'; @@ -93,7 +91,9 @@ class _HowToPlayDialogState extends State { return WillPopScope( onWillPop: () { widget.onDismissCallback.call(); - context.read().play(PinballAudio.ioPinballVoiceOver); + context + .read() + .play(PinballAudio.ioPinballVoiceOver); return Future.value(true); }, child: PinballDialog( @@ -242,7 +242,7 @@ class _DesktopFlipperControls extends StatelessWidget { children: [ Text( l10n.flipperControls, - style: Theme.of(context).textTheme.subtitle2, + style: Theme.of(context).textTheme.headline4, ), const SizedBox(height: 10), Column( diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index aa1a24f6..a9b12291 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -148,8 +148,78 @@ "@loading": { "description": "Text shown to indicate loading times" }, + "replay": "Replay", + "@replay": { + "description": "Text displayed on the share page for replay button" + }, "ioPinball": "I/O Pinball", "@ioPinball": { "description": "I/O Pinball - Name of the game" + }, + "shareYourScore": "Share your score", + "@shareYourScore": { + "description": "Text shown on title of info screen" + }, + "andChallengeYourFriends": "AND CHALLENGE YOUR FRIENDS", + "@challengeYourFriends": { + "description": "Text shown on title of info screen" + }, + "share": "SHARE", + "@share": { + "description": "Text for share link on info screen." + }, + "gotoIO": "GO TO I/O", + "@gotoIO": { + "description": "Text for going to I/O site link on info screen." + }, + "learnMore": "Learn more about building games in Flutter with", + "@learnMore": { + "description": "Text shown on description of info screen" + }, + "firebaseOr": "Firebase or dive right into the", + "@firebaseOr": { + "description": "Text shown on description of info screen" + }, + "openSourceCode": "open source code.", + "@openSourceCode": { + "description": "Text shown on description of info screen" + }, + "enter": "Enter", + "@enter": { + "description": "Text shown on the mobile controls enter button" + }, + "initialsErrorTitle": "Uh-oh... well, that didnโ€™t work", + "@initialsErrorTitle": { + "description": "Title shown when the initials submission fails" + }, + "initialsErrorMessage": "Please try a different combination of letters", + "@initialsErrorMessage": { + "description": "Message on shown when the initials submission fails" + }, + "leaderboardErrorMessage": "No connection. Leaderboard and sharing functionality is unavailable.", + "@leaderboardErrorMessage": { + "description": "Text shown when the leaderboard had an error while loading" + } + , + "letEveryone": "Let everyone know about I/O Pinball", + "@letEveryone": { + "description": "Text displayed on share screen for description" + }, + "bySharingYourScore": "by sharing your score to your preferred", + "@bySharingYourScore": { + "description": "Text displayed on share screen for description" + }, + "socialMediaAccount": "social media account!", + "@socialMediaAccount": { + "description": "Text displayed on share screen for description" + }, + "iGotScoreAtPinball": "I got {score} at the #IOPinball machine, can you beat my score? See you at #GoogleIO!", + "@iGotScoreAtPinball": { + "description": "Text to share score on Social Network", + "placeholders": { + "score": { + "type": "int" + } + } } } diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 0945f30f..17c891b5 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flutter/widgets.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; diff --git a/lib/main_development.dart b/lib/main.dart similarity index 74% rename from lib/main_development.dart rename to lib/main.dart index 67d83b81..cb8c78da 100644 --- a/lib/main_development.dart +++ b/lib/main.dart @@ -6,12 +6,15 @@ import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/app/app.dart'; import 'package:pinball/bootstrap.dart'; import 'package:pinball_audio/pinball_audio.dart'; +import 'package:share_repository/share_repository.dart'; void main() { bootstrap((firestore, firebaseAuth) async { final leaderboardRepository = LeaderboardRepository(firestore); + const shareRepository = + ShareRepository(appUrl: ShareRepository.pinballGameUrl); final authenticationRepository = AuthenticationRepository(firebaseAuth); - final pinballPlayer = PinballPlayer(); + final pinballAudioPlayer = PinballAudioPlayer(); unawaited( Firebase.initializeApp().then( (_) => authenticationRepository.authenticateAnonymously(), @@ -20,7 +23,8 @@ void main() { return App( authenticationRepository: authenticationRepository, leaderboardRepository: leaderboardRepository, - pinballPlayer: pinballPlayer, + shareRepository: shareRepository, + pinballAudioPlayer: pinballAudioPlayer, ); }); } diff --git a/lib/main_production.dart b/lib/main_production.dart deleted file mode 100644 index 67d83b81..00000000 --- a/lib/main_production.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'dart:async'; - -import 'package:authentication_repository/authentication_repository.dart'; -import 'package:firebase_core/firebase_core.dart'; -import 'package:leaderboard_repository/leaderboard_repository.dart'; -import 'package:pinball/app/app.dart'; -import 'package:pinball/bootstrap.dart'; -import 'package:pinball_audio/pinball_audio.dart'; - -void main() { - bootstrap((firestore, firebaseAuth) async { - final leaderboardRepository = LeaderboardRepository(firestore); - final authenticationRepository = AuthenticationRepository(firebaseAuth); - final pinballPlayer = PinballPlayer(); - unawaited( - Firebase.initializeApp().then( - (_) => authenticationRepository.authenticateAnonymously(), - ), - ); - return App( - authenticationRepository: authenticationRepository, - leaderboardRepository: leaderboardRepository, - pinballPlayer: pinballPlayer, - ); - }); -} diff --git a/lib/main_staging.dart b/lib/main_staging.dart deleted file mode 100644 index 67d83b81..00000000 --- a/lib/main_staging.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'dart:async'; - -import 'package:authentication_repository/authentication_repository.dart'; -import 'package:firebase_core/firebase_core.dart'; -import 'package:leaderboard_repository/leaderboard_repository.dart'; -import 'package:pinball/app/app.dart'; -import 'package:pinball/bootstrap.dart'; -import 'package:pinball_audio/pinball_audio.dart'; - -void main() { - bootstrap((firestore, firebaseAuth) async { - final leaderboardRepository = LeaderboardRepository(firestore); - final authenticationRepository = AuthenticationRepository(firebaseAuth); - final pinballPlayer = PinballPlayer(); - unawaited( - Firebase.initializeApp().then( - (_) => authenticationRepository.authenticateAnonymously(), - ), - ); - return App( - authenticationRepository: authenticationRepository, - leaderboardRepository: leaderboardRepository, - pinballPlayer: pinballPlayer, - ); - }); -} diff --git a/lib/more_information/more_information_dialog.dart b/lib/more_information/more_information_dialog.dart index 179c06f5..b9a9ddbb 100644 --- a/lib/more_information/more_information_dialog.dart +++ b/lib/more_information/more_information_dialog.dart @@ -55,8 +55,6 @@ class _LinkBoxHeader extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = context.l10n; - final indent = MediaQuery.of(context).size.width / 5; - return Column( children: [ Text( @@ -68,11 +66,9 @@ class _LinkBoxHeader extends StatelessWidget { overflow: TextOverflow.ellipsis, ), const SizedBox(height: 12), - Divider( - color: PinballColors.white, - endIndent: indent, - indent: indent, - thickness: 2, + const SizedBox( + width: 200, + child: Divider(color: PinballColors.white, thickness: 2), ), ], ); diff --git a/lib/select_character/cubit/character_theme_cubit.dart b/lib/select_character/cubit/character_theme_cubit.dart index 84792a71..2be3b88b 100644 --- a/lib/select_character/cubit/character_theme_cubit.dart +++ b/lib/select_character/cubit/character_theme_cubit.dart @@ -1,6 +1,3 @@ -// ignore_for_file: public_member_api_docs -// TODO(allisonryan0002): Document this section when the API is stable. - import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:pinball_theme/pinball_theme.dart'; diff --git a/lib/select_character/cubit/character_theme_state.dart b/lib/select_character/cubit/character_theme_state.dart index a1669f69..eb4f1279 100644 --- a/lib/select_character/cubit/character_theme_state.dart +++ b/lib/select_character/cubit/character_theme_state.dart @@ -1,6 +1,3 @@ -// ignore_for_file: public_member_api_docs -// TODO(allisonryan0002): Document this section when the API is stable. - part of 'character_theme_cubit.dart'; class CharacterThemeState extends Equatable { diff --git a/lib/select_character/view/character_selection_page.dart b/lib/select_character/view/character_selection_page.dart index 1f7b0374..2355d6cc 100644 --- a/lib/select_character/view/character_selection_page.dart +++ b/lib/select_character/view/character_selection_page.dart @@ -69,9 +69,9 @@ class _CharacterGrid extends StatelessWidget { child: Column( children: [ _Character( - key: const Key('sparky_character_selection'), - character: const SparkyTheme(), - isSelected: state.isSparkySelected, + key: const Key('dash_character_selection'), + character: const DashTheme(), + isSelected: state.isDashSelected, ), const SizedBox(height: 6), _Character( @@ -87,9 +87,9 @@ class _CharacterGrid extends StatelessWidget { child: Column( children: [ _Character( - key: const Key('dash_character_selection'), - character: const DashTheme(), - isSelected: state.isDashSelected, + key: const Key('sparky_character_selection'), + character: const SparkyTheme(), + isSelected: state.isSparkySelected, ), const SizedBox(height: 6), _Character( diff --git a/lib/start_game/bloc/start_game_bloc.dart b/lib/start_game/bloc/start_game_bloc.dart index 3a96b57b..5892cd80 100644 --- a/lib/start_game/bloc/start_game_bloc.dart +++ b/lib/start_game/bloc/start_game_bloc.dart @@ -11,6 +11,7 @@ class StartGameBloc extends Bloc { /// {@macro start_game_bloc} StartGameBloc() : super(const StartGameState.initial()) { on(_onPlayTapped); + on(_onReplayTapped); on(_onCharacterSelected); on(_onHowToPlayFinished); } @@ -26,6 +27,17 @@ class StartGameBloc extends Bloc { ); } + void _onReplayTapped( + ReplayTapped event, + Emitter emit, + ) { + emit( + state.copyWith( + status: StartGameStatus.selectCharacter, + ), + ); + } + void _onCharacterSelected( CharacterSelected event, Emitter emit, diff --git a/lib/start_game/bloc/start_game_event.dart b/lib/start_game/bloc/start_game_event.dart index ce164e97..ae216c32 100644 --- a/lib/start_game/bloc/start_game_event.dart +++ b/lib/start_game/bloc/start_game_event.dart @@ -19,6 +19,17 @@ class PlayTapped extends StartGameEvent { List get props => []; } +/// {@template replay_tapped} +/// Replay tapped event. +/// {@endtemplate} +class ReplayTapped extends StartGameEvent { + /// {@macro replay_tapped} + const ReplayTapped(); + + @override + List get props => []; +} + /// {@template character_selected} /// Character selected event. /// {@endtemplate} diff --git a/packages/pinball_audio/assets/sfx/android.mp3 b/packages/pinball_audio/assets/sfx/android.mp3 new file mode 100644 index 00000000..33bebe07 Binary files /dev/null and b/packages/pinball_audio/assets/sfx/android.mp3 differ diff --git a/packages/pinball_audio/assets/sfx/cow_moo.mp3 b/packages/pinball_audio/assets/sfx/cow_moo.mp3 new file mode 100644 index 00000000..ce69e941 Binary files /dev/null and b/packages/pinball_audio/assets/sfx/cow_moo.mp3 differ diff --git a/packages/pinball_audio/assets/sfx/dash.mp3 b/packages/pinball_audio/assets/sfx/dash.mp3 new file mode 100644 index 00000000..1180fc4e Binary files /dev/null and b/packages/pinball_audio/assets/sfx/dash.mp3 differ diff --git a/packages/pinball_audio/assets/sfx/dino.mp3 b/packages/pinball_audio/assets/sfx/dino.mp3 new file mode 100644 index 00000000..e8e4a1d5 Binary files /dev/null and b/packages/pinball_audio/assets/sfx/dino.mp3 differ diff --git a/packages/pinball_audio/assets/sfx/kicker_a.mp3 b/packages/pinball_audio/assets/sfx/kicker_a.mp3 new file mode 100644 index 00000000..475cbc13 Binary files /dev/null and b/packages/pinball_audio/assets/sfx/kicker_a.mp3 differ diff --git a/packages/pinball_audio/assets/sfx/kicker_b.mp3 b/packages/pinball_audio/assets/sfx/kicker_b.mp3 new file mode 100644 index 00000000..2b1bdfbc Binary files /dev/null and b/packages/pinball_audio/assets/sfx/kicker_b.mp3 differ diff --git a/packages/pinball_audio/lib/gen/assets.gen.dart b/packages/pinball_audio/lib/gen/assets.gen.dart index 08e83d87..c8b66234 100644 --- a/packages/pinball_audio/lib/gen/assets.gen.dart +++ b/packages/pinball_audio/lib/gen/assets.gen.dart @@ -14,11 +14,17 @@ class $AssetsMusicGen { class $AssetsSfxGen { const $AssetsSfxGen(); + String get android => 'assets/sfx/android.mp3'; String get bumperA => 'assets/sfx/bumper_a.mp3'; String get bumperB => 'assets/sfx/bumper_b.mp3'; + String get cowMoo => 'assets/sfx/cow_moo.mp3'; + String get dash => 'assets/sfx/dash.mp3'; + String get dino => 'assets/sfx/dino.mp3'; String get gameOverVoiceOver => 'assets/sfx/game_over_voice_over.mp3'; String get google => 'assets/sfx/google.mp3'; String get ioPinballVoiceOver => 'assets/sfx/io_pinball_voice_over.mp3'; + String get kickerA => 'assets/sfx/kicker_a.mp3'; + String get kickerB => 'assets/sfx/kicker_b.mp3'; String get launcher => 'assets/sfx/launcher.mp3'; String get sparky => 'assets/sfx/sparky.mp3'; } diff --git a/packages/pinball_audio/lib/src/pinball_audio.dart b/packages/pinball_audio/lib/src/pinball_audio.dart index 95c993c5..98074fc5 100644 --- a/packages/pinball_audio/lib/src/pinball_audio.dart +++ b/packages/pinball_audio/lib/src/pinball_audio.dart @@ -1,33 +1,49 @@ import 'dart:math'; import 'package:audioplayers/audioplayers.dart'; +import 'package:clock/clock.dart'; import 'package:flame_audio/audio_pool.dart'; import 'package:flame_audio/flame_audio.dart'; import 'package:flutter/material.dart'; import 'package:pinball_audio/gen/assets.gen.dart'; -/// Sounds available for play +/// Sounds available to play. enum PinballAudio { - /// Google + /// Google. google, - /// Bumper + /// Bumper. bumper, - /// Background music + /// Cow moo. + cowMoo, + + /// Background music. backgroundMusic, - /// IO Pinball voice over + /// IO Pinball voice over. ioPinballVoiceOver, - /// Game over + /// Game over. gameOverVoiceOver, - /// Launcher + /// Launcher. launcher, - /// Sparky + /// Kicker. + kicker, + + /// Sparky. sparky, + + /// Android + android, + + /// Dino + dino, + + /// Dash + dash, } /// Defines the contract of the creation of an [AudioPool]. @@ -100,48 +116,83 @@ class _LoopAudio extends _Audio { } } -class _BumperAudio extends _Audio { - _BumperAudio({ +class _RandomABAudio extends _Audio { + _RandomABAudio({ required this.createAudioPool, required this.seed, + required this.audioAssetA, + required this.audioAssetB, + this.volume, }); final CreateAudioPool createAudioPool; final Random seed; + final String audioAssetA; + final String audioAssetB; + final double? volume; - late AudioPool bumperA; - late AudioPool bumperB; + late AudioPool audioA; + late AudioPool audioB; @override Future load() async { await Future.wait( [ createAudioPool( - prefixFile(Assets.sfx.bumperA), + prefixFile(audioAssetA), maxPlayers: 4, prefix: '', - ).then((pool) => bumperA = pool), + ).then((pool) => audioA = pool), createAudioPool( - prefixFile(Assets.sfx.bumperB), + prefixFile(audioAssetB), maxPlayers: 4, prefix: '', - ).then((pool) => bumperB = pool), + ).then((pool) => audioB = pool), ], ); } @override void play() { - (seed.nextBool() ? bumperA : bumperB).start(volume: 0.6); + (seed.nextBool() ? audioA : audioB).start(volume: volume ?? 1); + } +} + +class _ThrottledAudio extends _Audio { + _ThrottledAudio({ + required this.preCacheSingleAudio, + required this.playSingleAudio, + required this.path, + required this.duration, + }); + + final PreCacheSingleAudio preCacheSingleAudio; + final PlaySingleAudio playSingleAudio; + final String path; + final Duration duration; + + DateTime? _lastPlayed; + + @override + Future load() => preCacheSingleAudio(prefixFile(path)); + + @override + void play() { + final now = clock.now(); + if (_lastPlayed == null || + (_lastPlayed != null && now.difference(_lastPlayed!) > duration)) { + _lastPlayed = now; + playSingleAudio(prefixFile(path)); + } } } -/// {@template pinball_player} -/// Sound manager for the pinball game +/// {@template pinball_audio_player} +/// Sound manager for the pinball game. /// {@endtemplate} -class PinballPlayer { - /// {@macro pinball_player} - PinballPlayer({ +class PinballAudioPlayer { + /// {@macro pinball_audio_player} + PinballAudioPlayer({ CreateAudioPool? createAudioPool, PlaySingleAudio? playSingleAudio, LoopSingleAudio? loopSingleAudio, @@ -169,6 +220,21 @@ class PinballPlayer { playSingleAudio: _playSingleAudio, path: Assets.sfx.sparky, ), + PinballAudio.dino: _SimplePlayAudio( + preCacheSingleAudio: _preCacheSingleAudio, + playSingleAudio: _playSingleAudio, + path: Assets.sfx.dino, + ), + PinballAudio.dash: _SimplePlayAudio( + preCacheSingleAudio: _preCacheSingleAudio, + playSingleAudio: _playSingleAudio, + path: Assets.sfx.dash, + ), + PinballAudio.android: _SimplePlayAudio( + preCacheSingleAudio: _preCacheSingleAudio, + playSingleAudio: _playSingleAudio, + path: Assets.sfx.android, + ), PinballAudio.launcher: _SimplePlayAudio( preCacheSingleAudio: _preCacheSingleAudio, playSingleAudio: _playSingleAudio, @@ -184,9 +250,25 @@ class PinballPlayer { playSingleAudio: _playSingleAudio, path: Assets.sfx.gameOverVoiceOver, ), - PinballAudio.bumper: _BumperAudio( + PinballAudio.bumper: _RandomABAudio( createAudioPool: _createAudioPool, seed: _seed, + audioAssetA: Assets.sfx.bumperA, + audioAssetB: Assets.sfx.bumperB, + volume: 0.6, + ), + PinballAudio.kicker: _RandomABAudio( + createAudioPool: _createAudioPool, + seed: _seed, + audioAssetA: Assets.sfx.kickerA, + audioAssetB: Assets.sfx.kickerB, + volume: 0.6, + ), + PinballAudio.cowMoo: _ThrottledAudio( + preCacheSingleAudio: _preCacheSingleAudio, + playSingleAudio: _playSingleAudio, + path: Assets.sfx.cowMoo, + duration: const Duration(seconds: 2), ), PinballAudio.backgroundMusic: _LoopAudio( preCacheSingleAudio: _preCacheSingleAudio, @@ -208,19 +290,19 @@ class PinballPlayer { final Random _seed; - /// Registered audios on the Player + /// Registered audios on the Player. @visibleForTesting // ignore: library_private_types_in_public_api late final Map audios; - /// Loads the sounds effects into the memory + /// Loads the sounds effects into the memory. List> load() { _configureAudioCache(FlameAudio.audioCache); return audios.values.map((a) => a.load()).toList(); } - /// Plays the received auido + /// Plays the received audio. void play(PinballAudio audio) { assert( audios.containsKey(audio), diff --git a/packages/pinball_audio/pubspec.yaml b/packages/pinball_audio/pubspec.yaml index 74713dfa..8c99d1fc 100644 --- a/packages/pinball_audio/pubspec.yaml +++ b/packages/pinball_audio/pubspec.yaml @@ -8,6 +8,7 @@ environment: dependencies: audioplayers: ^0.20.1 + clock: ^1.1.0 flame_audio: ^1.0.1 flutter: sdk: flutter diff --git a/packages/pinball_audio/test/src/pinball_audio_test.dart b/packages/pinball_audio/test/src/pinball_audio_test.dart index 39060eb2..df21b1ad 100644 --- a/packages/pinball_audio/test/src/pinball_audio_test.dart +++ b/packages/pinball_audio/test/src/pinball_audio_test.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:audioplayers/audioplayers.dart'; +import 'package:clock/clock.dart'; import 'package:flame_audio/audio_pool.dart'; import 'package:flame_audio/flame_audio.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -43,6 +44,8 @@ class _MockPreCacheSingleAudio extends Mock implements _PreCacheSingleAudio {} class _MockRandom extends Mock implements Random {} +class _MockClock extends Mock implements Clock {} + void main() { group('PinballAudio', () { late _MockCreateAudioPool createAudioPool; @@ -51,7 +54,7 @@ void main() { late _MockLoopSingleAudio loopSingleAudio; late _PreCacheSingleAudio preCacheSingleAudio; late Random seed; - late PinballPlayer player; + late PinballAudioPlayer audioPlayer; setUpAll(() { registerFallbackValue(_MockAudioCache()); @@ -81,7 +84,7 @@ void main() { seed = _MockRandom(); - player = PinballPlayer( + audioPlayer = PinballAudioPlayer( configureAudioCache: configureAudioCache.onCall, createAudioPool: createAudioPool.onCall, playSingleAudio: playSingleAudio.onCall, @@ -92,12 +95,12 @@ void main() { }); test('can be instantiated', () { - expect(PinballPlayer(), isNotNull); + expect(PinballAudioPlayer(), isNotNull); }); group('load', () { test('creates the bumpers pools', () async { - await Future.wait(player.load()); + await Future.wait(audioPlayer.load()); verify( () => createAudioPool.onCall( @@ -116,26 +119,46 @@ void main() { ).called(1); }); + test('creates the kicker pools', () async { + await Future.wait(audioPlayer.load()); + + verify( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerA}', + maxPlayers: 4, + prefix: '', + ), + ).called(1); + + verify( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerB}', + maxPlayers: 4, + prefix: '', + ), + ).called(1); + }); + test('configures the audio cache instance', () async { - await Future.wait(player.load()); + await Future.wait(audioPlayer.load()); verify(() => configureAudioCache.onCall(FlameAudio.audioCache)) .called(1); }); test('sets the correct prefix', () async { - player = PinballPlayer( + audioPlayer = PinballAudioPlayer( createAudioPool: createAudioPool.onCall, playSingleAudio: playSingleAudio.onCall, preCacheSingleAudio: preCacheSingleAudio.onCall, ); - await Future.wait(player.load()); + await Future.wait(audioPlayer.load()); expect(FlameAudio.audioCache.prefix, equals('')); }); test('pre cache the assets', () async { - await Future.wait(player.load()); + await Future.wait(audioPlayer.load()); verify( () => preCacheSingleAudio @@ -145,6 +168,18 @@ void main() { () => preCacheSingleAudio .onCall('packages/pinball_audio/assets/sfx/sparky.mp3'), ).called(1); + verify( + () => preCacheSingleAudio + .onCall('packages/pinball_audio/assets/sfx/dino.mp3'), + ).called(1); + verify( + () => preCacheSingleAudio + .onCall('packages/pinball_audio/assets/sfx/android.mp3'), + ).called(1); + verify( + () => preCacheSingleAudio + .onCall('packages/pinball_audio/assets/sfx/dash.mp3'), + ).called(1); verify( () => preCacheSingleAudio.onCall( 'packages/pinball_audio/assets/sfx/io_pinball_voice_over.mp3', @@ -159,6 +194,10 @@ void main() { () => preCacheSingleAudio .onCall('packages/pinball_audio/assets/sfx/launcher.mp3'), ).called(1); + verify( + () => preCacheSingleAudio + .onCall('packages/pinball_audio/assets/sfx/cow_moo.mp3'), + ).called(1); verify( () => preCacheSingleAudio .onCall('packages/pinball_audio/assets/music/background.mp3'), @@ -197,8 +236,8 @@ void main() { group('when seed is true', () { test('plays the bumper A sound pool', () async { when(seed.nextBool).thenReturn(true); - await Future.wait(player.load()); - player.play(PinballAudio.bumper); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.bumper); verify(() => bumperAPool.start(volume: 0.6)).called(1); }); @@ -207,18 +246,103 @@ void main() { group('when seed is false', () { test('plays the bumper B sound pool', () async { when(seed.nextBool).thenReturn(false); - await Future.wait(player.load()); - player.play(PinballAudio.bumper); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.bumper); verify(() => bumperBPool.start(volume: 0.6)).called(1); }); }); }); + group('kicker', () { + late AudioPool kickerAPool; + late AudioPool kickerBPool; + + setUp(() { + kickerAPool = _MockAudioPool(); + when(() => kickerAPool.start(volume: any(named: 'volume'))) + .thenAnswer((_) async => () {}); + when( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerA}', + maxPlayers: any(named: 'maxPlayers'), + prefix: any(named: 'prefix'), + ), + ).thenAnswer((_) async => kickerAPool); + + kickerBPool = _MockAudioPool(); + when(() => kickerBPool.start(volume: any(named: 'volume'))) + .thenAnswer((_) async => () {}); + when( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerB}', + maxPlayers: any(named: 'maxPlayers'), + prefix: any(named: 'prefix'), + ), + ).thenAnswer((_) async => kickerBPool); + }); + + group('when seed is true', () { + test('plays the kicker A sound pool', () async { + when(seed.nextBool).thenReturn(true); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.kicker); + + verify(() => kickerAPool.start(volume: 0.6)).called(1); + }); + }); + + group('when seed is false', () { + test('plays the kicker B sound pool', () async { + when(seed.nextBool).thenReturn(false); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.kicker); + + verify(() => kickerBPool.start(volume: 0.6)).called(1); + }); + }); + }); + + group('cow moo', () { + test('plays the correct file', () async { + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.cowMoo); + + verify( + () => playSingleAudio + .onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'), + ).called(1); + }); + + test('only plays the sound again after 2 seconds', () async { + final clock = _MockClock(); + await withClock(clock, () async { + when(clock.now).thenReturn(DateTime(2022)); + await Future.wait(audioPlayer.load()); + audioPlayer + ..play(PinballAudio.cowMoo) + ..play(PinballAudio.cowMoo); + + verify( + () => playSingleAudio + .onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'), + ).called(1); + + when(clock.now).thenReturn(DateTime(2022, 1, 1, 1, 2)); + audioPlayer.play(PinballAudio.cowMoo); + + verify( + () => playSingleAudio + .onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'), + ).called(1); + }); + }); + }); + group('google', () { test('plays the correct file', () async { - await Future.wait(player.load()); - player.play(PinballAudio.google); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.google); verify( () => playSingleAudio @@ -229,8 +353,8 @@ void main() { group('sparky', () { test('plays the correct file', () async { - await Future.wait(player.load()); - player.play(PinballAudio.sparky); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.sparky); verify( () => playSingleAudio @@ -239,10 +363,46 @@ void main() { }); }); + group('dino', () { + test('plays the correct file', () async { + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.dino); + + verify( + () => playSingleAudio + .onCall('packages/pinball_audio/${Assets.sfx.dino}'), + ).called(1); + }); + }); + + group('android', () { + test('plays the correct file', () async { + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.android); + + verify( + () => playSingleAudio + .onCall('packages/pinball_audio/${Assets.sfx.android}'), + ).called(1); + }); + }); + + group('dash', () { + test('plays the correct file', () async { + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.dash); + + verify( + () => playSingleAudio + .onCall('packages/pinball_audio/${Assets.sfx.dash}'), + ).called(1); + }); + }); + group('launcher', () { test('plays the correct file', () async { - await Future.wait(player.load()); - player.play(PinballAudio.launcher); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.launcher); verify( () => playSingleAudio @@ -253,8 +413,8 @@ void main() { group('ioPinballVoiceOver', () { test('plays the correct file', () async { - await Future.wait(player.load()); - player.play(PinballAudio.ioPinballVoiceOver); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.ioPinballVoiceOver); verify( () => playSingleAudio.onCall( @@ -266,8 +426,8 @@ void main() { group('gameOverVoiceOver', () { test('plays the correct file', () async { - await Future.wait(player.load()); - player.play(PinballAudio.gameOverVoiceOver); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.gameOverVoiceOver); verify( () => playSingleAudio.onCall( @@ -279,8 +439,8 @@ void main() { group('backgroundMusic', () { test('plays the correct file', () async { - await Future.wait(player.load()); - player.play(PinballAudio.backgroundMusic); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.backgroundMusic); verify( () => loopSingleAudio @@ -292,10 +452,13 @@ void main() { test( 'throws assertions error when playing an unregistered audio', () async { - player.audios.remove(PinballAudio.google); - await Future.wait(player.load()); + audioPlayer.audios.remove(PinballAudio.google); + await Future.wait(audioPlayer.load()); - expect(() => player.play(PinballAudio.google), throwsAssertionError); + expect( + () => audioPlayer.play(PinballAudio.google), + throwsAssertionError, + ); }, ); }); diff --git a/packages/pinball_components/analysis_options.yaml b/packages/pinball_components/analysis_options.yaml index f8155aa6..eb141d0e 100644 --- a/packages/pinball_components/analysis_options.yaml +++ b/packages/pinball_components/analysis_options.yaml @@ -2,3 +2,6 @@ include: package:very_good_analysis/analysis_options.2.4.0.yaml analyzer: exclude: - lib/**/*.gen.dart +linter: + rules: + public_member_api_docs: false \ No newline at end of file diff --git a/packages/pinball_components/assets/images/android/ramp/board-opening.png b/packages/pinball_components/assets/images/android/ramp/board_opening.png similarity index 100% rename from packages/pinball_components/assets/images/android/ramp/board-opening.png rename to packages/pinball_components/assets/images/android/ramp/board_opening.png diff --git a/packages/pinball_components/assets/images/android/ramp/railing-background.png b/packages/pinball_components/assets/images/android/ramp/railing_background.png similarity index 100% rename from packages/pinball_components/assets/images/android/ramp/railing-background.png rename to packages/pinball_components/assets/images/android/ramp/railing_background.png diff --git a/packages/pinball_components/assets/images/android/ramp/railing-foreground.png b/packages/pinball_components/assets/images/android/ramp/railing_foreground.png similarity index 100% rename from packages/pinball_components/assets/images/android/ramp/railing-foreground.png rename to packages/pinball_components/assets/images/android/ramp/railing_foreground.png diff --git a/packages/pinball_components/assets/images/android/spaceship/light-beam.png b/packages/pinball_components/assets/images/android/spaceship/light_beam.png similarity index 100% rename from packages/pinball_components/assets/images/android/spaceship/light-beam.png rename to packages/pinball_components/assets/images/android/spaceship/light_beam.png diff --git a/packages/pinball_components/assets/images/backbox/button/facebook.png b/packages/pinball_components/assets/images/backbox/button/facebook.png new file mode 100644 index 00000000..f6d29ab2 Binary files /dev/null and b/packages/pinball_components/assets/images/backbox/button/facebook.png differ diff --git a/packages/pinball_components/assets/images/backbox/button/twitter.png b/packages/pinball_components/assets/images/backbox/button/twitter.png new file mode 100644 index 00000000..f109a4b8 Binary files /dev/null and b/packages/pinball_components/assets/images/backbox/button/twitter.png differ diff --git a/packages/pinball_components/assets/images/backbox/display-divider.png b/packages/pinball_components/assets/images/backbox/display_divider.png similarity index 100% rename from packages/pinball_components/assets/images/backbox/display-divider.png rename to packages/pinball_components/assets/images/backbox/display_divider.png diff --git a/packages/pinball_components/assets/images/backbox/display_title_decoration.png b/packages/pinball_components/assets/images/backbox/display_title_decoration.png new file mode 100644 index 00000000..71f8d609 Binary files /dev/null and b/packages/pinball_components/assets/images/backbox/display_title_decoration.png differ diff --git a/packages/pinball_components/assets/images/backbox/marquee.png b/packages/pinball_components/assets/images/backbox/marquee.png index ee98a495..8603c9b8 100644 Binary files a/packages/pinball_components/assets/images/backbox/marquee.png and b/packages/pinball_components/assets/images/backbox/marquee.png differ diff --git a/packages/pinball_components/assets/images/ball/flame_effect.png b/packages/pinball_components/assets/images/ball/flame_effect.png index 03a6fca6..b1397f36 100644 Binary files a/packages/pinball_components/assets/images/ball/flame_effect.png and b/packages/pinball_components/assets/images/ball/flame_effect.png differ diff --git a/packages/pinball_components/assets/images/baseboard/left.png b/packages/pinball_components/assets/images/baseboard/left.png index d13b4e31..552c6e1d 100644 Binary files a/packages/pinball_components/assets/images/baseboard/left.png and b/packages/pinball_components/assets/images/baseboard/left.png differ diff --git a/packages/pinball_components/assets/images/baseboard/right.png b/packages/pinball_components/assets/images/baseboard/right.png index 8ad93045..9088d7e0 100644 Binary files a/packages/pinball_components/assets/images/baseboard/right.png and b/packages/pinball_components/assets/images/baseboard/right.png differ diff --git a/packages/pinball_components/assets/images/board-background.png b/packages/pinball_components/assets/images/board-background.png deleted file mode 100644 index dabf9026..00000000 Binary files a/packages/pinball_components/assets/images/board-background.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/board_background.png b/packages/pinball_components/assets/images/board_background.png new file mode 100644 index 00000000..41af91ec Binary files /dev/null and b/packages/pinball_components/assets/images/board_background.png differ diff --git a/packages/pinball_components/assets/images/boundary/bottom.png b/packages/pinball_components/assets/images/boundary/bottom.png index 806f7051..a9e5785b 100644 Binary files a/packages/pinball_components/assets/images/boundary/bottom.png and b/packages/pinball_components/assets/images/boundary/bottom.png differ diff --git a/packages/pinball_components/assets/images/boundary/outer-bottom.png b/packages/pinball_components/assets/images/boundary/outer-bottom.png deleted file mode 100644 index 508bcee8..00000000 Binary files a/packages/pinball_components/assets/images/boundary/outer-bottom.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/boundary/outer.png b/packages/pinball_components/assets/images/boundary/outer.png index 75ccdb6b..d7bc719e 100644 Binary files a/packages/pinball_components/assets/images/boundary/outer.png and b/packages/pinball_components/assets/images/boundary/outer.png differ diff --git a/packages/pinball_components/assets/images/boundary/outer_bottom.png b/packages/pinball_components/assets/images/boundary/outer_bottom.png new file mode 100644 index 00000000..9f5cd308 Binary files /dev/null and b/packages/pinball_components/assets/images/boundary/outer_bottom.png differ diff --git a/packages/pinball_components/assets/images/dash/animatronic.png b/packages/pinball_components/assets/images/dash/animatronic.png index 13f7b794..d2aa19c5 100644 Binary files a/packages/pinball_components/assets/images/dash/animatronic.png and b/packages/pinball_components/assets/images/dash/animatronic.png differ diff --git a/packages/pinball_components/assets/images/dino/animatronic/head.png b/packages/pinball_components/assets/images/dino/animatronic/head.png index 87332679..44ce80f2 100644 Binary files a/packages/pinball_components/assets/images/dino/animatronic/head.png and b/packages/pinball_components/assets/images/dino/animatronic/head.png differ diff --git a/packages/pinball_components/assets/images/dino/animatronic/mouth.png b/packages/pinball_components/assets/images/dino/animatronic/mouth.png index 4955bdf3..98b1386b 100644 Binary files a/packages/pinball_components/assets/images/dino/animatronic/mouth.png and b/packages/pinball_components/assets/images/dino/animatronic/mouth.png differ diff --git a/packages/pinball_components/assets/images/dino/bottom-wall.png b/packages/pinball_components/assets/images/dino/bottom_wall.png similarity index 100% rename from packages/pinball_components/assets/images/dino/bottom-wall.png rename to packages/pinball_components/assets/images/dino/bottom_wall.png diff --git a/packages/pinball_components/assets/images/dino/top-wall.png b/packages/pinball_components/assets/images/dino/top-wall.png deleted file mode 100644 index 7ee69411..00000000 Binary files a/packages/pinball_components/assets/images/dino/top-wall.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/dino/top_wall.png b/packages/pinball_components/assets/images/dino/top_wall.png new file mode 100644 index 00000000..76dc4f12 Binary files /dev/null and b/packages/pinball_components/assets/images/dino/top_wall.png differ diff --git a/packages/pinball_components/assets/images/dino/top-wall-tunnel.png b/packages/pinball_components/assets/images/dino/top_wall_tunnel.png similarity index 100% rename from packages/pinball_components/assets/images/dino/top-wall-tunnel.png rename to packages/pinball_components/assets/images/dino/top_wall_tunnel.png diff --git a/packages/pinball_components/assets/images/error_background.png b/packages/pinball_components/assets/images/error_background.png index 5aa6595f..73e934ca 100644 Binary files a/packages/pinball_components/assets/images/error_background.png and b/packages/pinball_components/assets/images/error_background.png differ diff --git a/packages/pinball_components/assets/images/flapper/back-support.png b/packages/pinball_components/assets/images/flapper/back_support.png similarity index 100% rename from packages/pinball_components/assets/images/flapper/back-support.png rename to packages/pinball_components/assets/images/flapper/back_support.png diff --git a/packages/pinball_components/assets/images/flapper/front-support.png b/packages/pinball_components/assets/images/flapper/front_support.png similarity index 100% rename from packages/pinball_components/assets/images/flapper/front-support.png rename to packages/pinball_components/assets/images/flapper/front_support.png diff --git a/packages/pinball_components/assets/images/flipper/left.png b/packages/pinball_components/assets/images/flipper/left.png index 3aefa225..190b5bb0 100644 Binary files a/packages/pinball_components/assets/images/flipper/left.png and b/packages/pinball_components/assets/images/flipper/left.png differ diff --git a/packages/pinball_components/assets/images/flipper/right.png b/packages/pinball_components/assets/images/flipper/right.png index 3627c86c..6bc46580 100644 Binary files a/packages/pinball_components/assets/images/flipper/right.png and b/packages/pinball_components/assets/images/flipper/right.png differ diff --git a/packages/pinball_components/assets/images/launch_ramp/background-railing.png b/packages/pinball_components/assets/images/launch_ramp/background-railing.png deleted file mode 100644 index aa7d5774..00000000 Binary files a/packages/pinball_components/assets/images/launch_ramp/background-railing.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/launch_ramp/background_railing.png b/packages/pinball_components/assets/images/launch_ramp/background_railing.png new file mode 100644 index 00000000..e9201957 Binary files /dev/null and b/packages/pinball_components/assets/images/launch_ramp/background_railing.png differ diff --git a/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png b/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png deleted file mode 100644 index f953fdf5..00000000 Binary files a/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/launch_ramp/foreground_railing.png b/packages/pinball_components/assets/images/launch_ramp/foreground_railing.png new file mode 100644 index 00000000..1a48f663 Binary files /dev/null and b/packages/pinball_components/assets/images/launch_ramp/foreground_railing.png differ diff --git a/packages/pinball_components/assets/images/launch_ramp/ramp.png b/packages/pinball_components/assets/images/launch_ramp/ramp.png index 61481f5a..6b2edde1 100644 Binary files a/packages/pinball_components/assets/images/launch_ramp/ramp.png and b/packages/pinball_components/assets/images/launch_ramp/ramp.png differ diff --git a/packages/pinball_components/assets/images/plunger/plunger.png b/packages/pinball_components/assets/images/plunger/plunger.png index 2ec6e001..df5812cd 100644 Binary files a/packages/pinball_components/assets/images/plunger/plunger.png and b/packages/pinball_components/assets/images/plunger/plunger.png differ diff --git a/packages/pinball_components/assets/images/plunger/rocket.png b/packages/pinball_components/assets/images/plunger/rocket.png index a8f89152..ef2df7e5 100644 Binary files a/packages/pinball_components/assets/images/plunger/rocket.png and b/packages/pinball_components/assets/images/plunger/rocket.png differ diff --git a/packages/pinball_components/assets/images/score/five-thousand.png b/packages/pinball_components/assets/images/score/five-thousand.png deleted file mode 100644 index d373e2e1..00000000 Binary files a/packages/pinball_components/assets/images/score/five-thousand.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/score/five_thousand.png b/packages/pinball_components/assets/images/score/five_thousand.png new file mode 100644 index 00000000..a2c37dec Binary files /dev/null and b/packages/pinball_components/assets/images/score/five_thousand.png differ diff --git a/packages/pinball_components/assets/images/score/one-million.png b/packages/pinball_components/assets/images/score/one-million.png deleted file mode 100644 index 5c7ec15b..00000000 Binary files a/packages/pinball_components/assets/images/score/one-million.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/score/one_million.png b/packages/pinball_components/assets/images/score/one_million.png new file mode 100644 index 00000000..fcbd4919 Binary files /dev/null and b/packages/pinball_components/assets/images/score/one_million.png differ diff --git a/packages/pinball_components/assets/images/score/twenty-thousand.png b/packages/pinball_components/assets/images/score/twenty-thousand.png deleted file mode 100644 index 2f9bfd57..00000000 Binary files a/packages/pinball_components/assets/images/score/twenty-thousand.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/score/twenty_thousand.png b/packages/pinball_components/assets/images/score/twenty_thousand.png new file mode 100644 index 00000000..cebdf3b5 Binary files /dev/null and b/packages/pinball_components/assets/images/score/twenty_thousand.png differ diff --git a/packages/pinball_components/assets/images/score/two-hundred-thousand.png b/packages/pinball_components/assets/images/score/two-hundred-thousand.png deleted file mode 100644 index a6f19db4..00000000 Binary files a/packages/pinball_components/assets/images/score/two-hundred-thousand.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/score/two_hundred_thousand.png b/packages/pinball_components/assets/images/score/two_hundred_thousand.png new file mode 100644 index 00000000..672b18a0 Binary files /dev/null and b/packages/pinball_components/assets/images/score/two_hundred_thousand.png differ diff --git a/packages/pinball_components/assets/images/signpost/active1.png b/packages/pinball_components/assets/images/signpost/active1.png index 78997bf6..23f5836f 100644 Binary files a/packages/pinball_components/assets/images/signpost/active1.png and b/packages/pinball_components/assets/images/signpost/active1.png differ diff --git a/packages/pinball_components/assets/images/signpost/active2.png b/packages/pinball_components/assets/images/signpost/active2.png index 39caa821..6f79aa47 100644 Binary files a/packages/pinball_components/assets/images/signpost/active2.png and b/packages/pinball_components/assets/images/signpost/active2.png differ diff --git a/packages/pinball_components/assets/images/signpost/active3.png b/packages/pinball_components/assets/images/signpost/active3.png index f43c190c..7d54547a 100644 Binary files a/packages/pinball_components/assets/images/signpost/active3.png and b/packages/pinball_components/assets/images/signpost/active3.png differ diff --git a/packages/pinball_components/assets/images/signpost/inactive.png b/packages/pinball_components/assets/images/signpost/inactive.png index 9fa23330..f0721f3c 100644 Binary files a/packages/pinball_components/assets/images/signpost/inactive.png and b/packages/pinball_components/assets/images/signpost/inactive.png differ diff --git a/packages/pinball_components/assets/images/skill_shot/decal.png b/packages/pinball_components/assets/images/skill_shot/decal.png index 120d70aa..02fbd5cf 100644 Binary files a/packages/pinball_components/assets/images/skill_shot/decal.png and b/packages/pinball_components/assets/images/skill_shot/decal.png differ diff --git a/packages/pinball_components/assets/images/skill_shot/dimmed.png b/packages/pinball_components/assets/images/skill_shot/dimmed.png index 7cc32bd4..4e3a5ca9 100644 Binary files a/packages/pinball_components/assets/images/skill_shot/dimmed.png and b/packages/pinball_components/assets/images/skill_shot/dimmed.png differ diff --git a/packages/pinball_components/assets/images/skill_shot/lit.png b/packages/pinball_components/assets/images/skill_shot/lit.png index d1bce99b..cc825171 100644 Binary files a/packages/pinball_components/assets/images/skill_shot/lit.png and b/packages/pinball_components/assets/images/skill_shot/lit.png differ diff --git a/packages/pinball_components/assets/images/skill_shot/pin.png b/packages/pinball_components/assets/images/skill_shot/pin.png index 5b64e1ab..61126586 100644 Binary files a/packages/pinball_components/assets/images/skill_shot/pin.png and b/packages/pinball_components/assets/images/skill_shot/pin.png differ diff --git a/packages/pinball_components/assets/images/sparky/animatronic.png b/packages/pinball_components/assets/images/sparky/animatronic.png index cc57e405..a19c3dac 100644 Binary files a/packages/pinball_components/assets/images/sparky/animatronic.png and b/packages/pinball_components/assets/images/sparky/animatronic.png differ diff --git a/packages/pinball_components/assets/images/sparky/computer/base.png b/packages/pinball_components/assets/images/sparky/computer/base.png index 188e4329..9abd60d8 100644 Binary files a/packages/pinball_components/assets/images/sparky/computer/base.png and b/packages/pinball_components/assets/images/sparky/computer/base.png differ diff --git a/packages/pinball_components/assets/images/sparky/computer/glow.png b/packages/pinball_components/assets/images/sparky/computer/glow.png index 7fd9a0c8..8190d131 100644 Binary files a/packages/pinball_components/assets/images/sparky/computer/glow.png and b/packages/pinball_components/assets/images/sparky/computer/glow.png differ diff --git a/packages/pinball_components/assets/images/sparky/computer/top.png b/packages/pinball_components/assets/images/sparky/computer/top.png index 085771cd..445a8027 100644 Binary files a/packages/pinball_components/assets/images/sparky/computer/top.png and b/packages/pinball_components/assets/images/sparky/computer/top.png differ diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index 0d603727..f233596c 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -3,6 +3,8 @@ /// FlutterGen /// ***************************************************** +// ignore_for_file: directives_ordering,unnecessary_import + import 'package:flutter/widgets.dart'; class $AssetsImagesGen { @@ -12,13 +14,19 @@ class $AssetsImagesGen { $AssetsImagesBackboxGen get backbox => const $AssetsImagesBackboxGen(); $AssetsImagesBallGen get ball => const $AssetsImagesBallGen(); $AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen(); + + /// File path: assets/images/board_background.png AssetGenImage get boardBackground => - const AssetGenImage('assets/images/board-background.png'); + const AssetGenImage('assets/images/board_background.png'); + $AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen(); $AssetsImagesDashGen get dash => const $AssetsImagesDashGen(); $AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen(); + + /// File path: assets/images/error_background.png AssetGenImage get errorBackground => const AssetGenImage('assets/images/error_background.png'); + $AssetsImagesFlapperGen get flapper => const $AssetsImagesFlapperGen(); $AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen(); $AssetsImagesGoogleWordGen get googleWord => @@ -51,8 +59,18 @@ class $AssetsImagesAndroidGen { class $AssetsImagesBackboxGen { const $AssetsImagesBackboxGen(); + $AssetsImagesBackboxButtonGen get button => + const $AssetsImagesBackboxButtonGen(); + + /// File path: assets/images/backbox/display_divider.png AssetGenImage get displayDivider => - const AssetGenImage('assets/images/backbox/display-divider.png'); + const AssetGenImage('assets/images/backbox/display_divider.png'); + + /// File path: assets/images/backbox/display_title_decoration.png + AssetGenImage get displayTitleDecoration => + const AssetGenImage('assets/images/backbox/display_title_decoration.png'); + + /// File path: assets/images/backbox/marquee.png AssetGenImage get marquee => const AssetGenImage('assets/images/backbox/marquee.png'); } @@ -60,6 +78,7 @@ class $AssetsImagesBackboxGen { class $AssetsImagesBallGen { const $AssetsImagesBallGen(); + /// File path: assets/images/ball/flame_effect.png AssetGenImage get flameEffect => const AssetGenImage('assets/images/ball/flame_effect.png'); } @@ -67,8 +86,11 @@ class $AssetsImagesBallGen { class $AssetsImagesBaseboardGen { const $AssetsImagesBaseboardGen(); + /// File path: assets/images/baseboard/left.png AssetGenImage get left => const AssetGenImage('assets/images/baseboard/left.png'); + + /// File path: assets/images/baseboard/right.png AssetGenImage get right => const AssetGenImage('assets/images/baseboard/right.png'); } @@ -76,19 +98,26 @@ class $AssetsImagesBaseboardGen { class $AssetsImagesBoundaryGen { const $AssetsImagesBoundaryGen(); + /// File path: assets/images/boundary/bottom.png AssetGenImage get bottom => const AssetGenImage('assets/images/boundary/bottom.png'); - AssetGenImage get outerBottom => - const AssetGenImage('assets/images/boundary/outer-bottom.png'); + + /// File path: assets/images/boundary/outer.png AssetGenImage get outer => const AssetGenImage('assets/images/boundary/outer.png'); + + /// File path: assets/images/boundary/outer_bottom.png + AssetGenImage get outerBottom => + const AssetGenImage('assets/images/boundary/outer_bottom.png'); } class $AssetsImagesDashGen { const $AssetsImagesDashGen(); + /// File path: assets/images/dash/animatronic.png AssetGenImage get animatronic => const AssetGenImage('assets/images/dash/animatronic.png'); + $AssetsImagesDashBumperGen get bumper => const $AssetsImagesDashBumperGen(); } @@ -97,30 +126,44 @@ class $AssetsImagesDinoGen { $AssetsImagesDinoAnimatronicGen get animatronic => const $AssetsImagesDinoAnimatronicGen(); + + /// File path: assets/images/dino/bottom_wall.png AssetGenImage get bottomWall => - const AssetGenImage('assets/images/dino/bottom-wall.png'); - AssetGenImage get topWallTunnel => - const AssetGenImage('assets/images/dino/top-wall-tunnel.png'); + const AssetGenImage('assets/images/dino/bottom_wall.png'); + + /// File path: assets/images/dino/top_wall.png AssetGenImage get topWall => - const AssetGenImage('assets/images/dino/top-wall.png'); + const AssetGenImage('assets/images/dino/top_wall.png'); + + /// File path: assets/images/dino/top_wall_tunnel.png + AssetGenImage get topWallTunnel => + const AssetGenImage('assets/images/dino/top_wall_tunnel.png'); } class $AssetsImagesFlapperGen { const $AssetsImagesFlapperGen(); + /// File path: assets/images/flapper/back_support.png AssetGenImage get backSupport => - const AssetGenImage('assets/images/flapper/back-support.png'); + const AssetGenImage('assets/images/flapper/back_support.png'); + + /// File path: assets/images/flapper/flap.png AssetGenImage get flap => const AssetGenImage('assets/images/flapper/flap.png'); + + /// File path: assets/images/flapper/front_support.png AssetGenImage get frontSupport => - const AssetGenImage('assets/images/flapper/front-support.png'); + const AssetGenImage('assets/images/flapper/front_support.png'); } class $AssetsImagesFlipperGen { const $AssetsImagesFlipperGen(); + /// File path: assets/images/flipper/left.png AssetGenImage get left => const AssetGenImage('assets/images/flipper/left.png'); + + /// File path: assets/images/flipper/right.png AssetGenImage get right => const AssetGenImage('assets/images/flipper/right.png'); } @@ -152,10 +195,15 @@ class $AssetsImagesKickerGen { class $AssetsImagesLaunchRampGen { const $AssetsImagesLaunchRampGen(); + /// File path: assets/images/launch_ramp/background_railing.png AssetGenImage get backgroundRailing => - const AssetGenImage('assets/images/launch_ramp/background-railing.png'); + const AssetGenImage('assets/images/launch_ramp/background_railing.png'); + + /// File path: assets/images/launch_ramp/foreground_railing.png AssetGenImage get foregroundRailing => - const AssetGenImage('assets/images/launch_ramp/foreground-railing.png'); + const AssetGenImage('assets/images/launch_ramp/foreground_railing.png'); + + /// File path: assets/images/launch_ramp/ramp.png AssetGenImage get ramp => const AssetGenImage('assets/images/launch_ramp/ramp.png'); } @@ -163,8 +211,11 @@ class $AssetsImagesLaunchRampGen { class $AssetsImagesMultiballGen { const $AssetsImagesMultiballGen(); + /// File path: assets/images/multiball/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/multiball/dimmed.png'); + + /// File path: assets/images/multiball/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/multiball/lit.png'); } @@ -182,8 +233,11 @@ class $AssetsImagesMultiplierGen { class $AssetsImagesPlungerGen { const $AssetsImagesPlungerGen(); + /// File path: assets/images/plunger/plunger.png AssetGenImage get plunger => const AssetGenImage('assets/images/plunger/plunger.png'); + + /// File path: assets/images/plunger/rocket.png AssetGenImage get rocket => const AssetGenImage('assets/images/plunger/rocket.png'); } @@ -191,25 +245,39 @@ class $AssetsImagesPlungerGen { class $AssetsImagesScoreGen { const $AssetsImagesScoreGen(); + /// File path: assets/images/score/five_thousand.png AssetGenImage get fiveThousand => - const AssetGenImage('assets/images/score/five-thousand.png'); + const AssetGenImage('assets/images/score/five_thousand.png'); + + /// File path: assets/images/score/one_million.png AssetGenImage get oneMillion => - const AssetGenImage('assets/images/score/one-million.png'); + const AssetGenImage('assets/images/score/one_million.png'); + + /// File path: assets/images/score/twenty_thousand.png AssetGenImage get twentyThousand => - const AssetGenImage('assets/images/score/twenty-thousand.png'); + const AssetGenImage('assets/images/score/twenty_thousand.png'); + + /// File path: assets/images/score/two_hundred_thousand.png AssetGenImage get twoHundredThousand => - const AssetGenImage('assets/images/score/two-hundred-thousand.png'); + const AssetGenImage('assets/images/score/two_hundred_thousand.png'); } class $AssetsImagesSignpostGen { const $AssetsImagesSignpostGen(); + /// File path: assets/images/signpost/active1.png AssetGenImage get active1 => const AssetGenImage('assets/images/signpost/active1.png'); + + /// File path: assets/images/signpost/active2.png AssetGenImage get active2 => const AssetGenImage('assets/images/signpost/active2.png'); + + /// File path: assets/images/signpost/active3.png AssetGenImage get active3 => const AssetGenImage('assets/images/signpost/active3.png'); + + /// File path: assets/images/signpost/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/signpost/inactive.png'); } @@ -217,12 +285,19 @@ class $AssetsImagesSignpostGen { class $AssetsImagesSkillShotGen { const $AssetsImagesSkillShotGen(); + /// File path: assets/images/skill_shot/decal.png AssetGenImage get decal => const AssetGenImage('assets/images/skill_shot/decal.png'); + + /// File path: assets/images/skill_shot/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/skill_shot/dimmed.png'); + + /// File path: assets/images/skill_shot/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/skill_shot/lit.png'); + + /// File path: assets/images/skill_shot/pin.png AssetGenImage get pin => const AssetGenImage('assets/images/skill_shot/pin.png'); } @@ -230,8 +305,11 @@ class $AssetsImagesSkillShotGen { class $AssetsImagesSlingshotGen { const $AssetsImagesSlingshotGen(); + /// File path: assets/images/slingshot/lower.png AssetGenImage get lower => const AssetGenImage('assets/images/slingshot/lower.png'); + + /// File path: assets/images/slingshot/upper.png AssetGenImage get upper => const AssetGenImage('assets/images/slingshot/upper.png'); } @@ -239,8 +317,10 @@ class $AssetsImagesSlingshotGen { class $AssetsImagesSparkyGen { const $AssetsImagesSparkyGen(); + /// File path: assets/images/sparky/animatronic.png AssetGenImage get animatronic => const AssetGenImage('assets/images/sparky/animatronic.png'); + $AssetsImagesSparkyBumperGen get bumper => const $AssetsImagesSparkyBumperGen(); $AssetsImagesSparkyComputerGen get computer => @@ -261,8 +341,11 @@ class $AssetsImagesAndroidBumperGen { class $AssetsImagesAndroidRailGen { const $AssetsImagesAndroidRailGen(); + /// File path: assets/images/android/rail/exit.png AssetGenImage get exit => const AssetGenImage('assets/images/android/rail/exit.png'); + + /// File path: assets/images/android/rail/main.png AssetGenImage get main => const AssetGenImage('assets/images/android/rail/main.png'); } @@ -272,27 +355,52 @@ class $AssetsImagesAndroidRampGen { $AssetsImagesAndroidRampArrowGen get arrow => const $AssetsImagesAndroidRampArrowGen(); + + /// File path: assets/images/android/ramp/board_opening.png AssetGenImage get boardOpening => - const AssetGenImage('assets/images/android/ramp/board-opening.png'); + const AssetGenImage('assets/images/android/ramp/board_opening.png'); + + /// File path: assets/images/android/ramp/main.png AssetGenImage get main => const AssetGenImage('assets/images/android/ramp/main.png'); + + /// File path: assets/images/android/ramp/railing_background.png AssetGenImage get railingBackground => - const AssetGenImage('assets/images/android/ramp/railing-background.png'); + const AssetGenImage('assets/images/android/ramp/railing_background.png'); + + /// File path: assets/images/android/ramp/railing_foreground.png AssetGenImage get railingForeground => - const AssetGenImage('assets/images/android/ramp/railing-foreground.png'); + const AssetGenImage('assets/images/android/ramp/railing_foreground.png'); } class $AssetsImagesAndroidSpaceshipGen { const $AssetsImagesAndroidSpaceshipGen(); + /// File path: assets/images/android/spaceship/animatronic.png AssetGenImage get animatronic => const AssetGenImage('assets/images/android/spaceship/animatronic.png'); + + /// File path: assets/images/android/spaceship/light_beam.png AssetGenImage get lightBeam => - const AssetGenImage('assets/images/android/spaceship/light-beam.png'); + const AssetGenImage('assets/images/android/spaceship/light_beam.png'); + + /// File path: assets/images/android/spaceship/saucer.png AssetGenImage get saucer => const AssetGenImage('assets/images/android/spaceship/saucer.png'); } +class $AssetsImagesBackboxButtonGen { + const $AssetsImagesBackboxButtonGen(); + + /// File path: assets/images/backbox/button/facebook.png + AssetGenImage get facebook => + const AssetGenImage('assets/images/backbox/button/facebook.png'); + + /// File path: assets/images/backbox/button/twitter.png + AssetGenImage get twitter => + const AssetGenImage('assets/images/backbox/button/twitter.png'); +} + class $AssetsImagesDashBumperGen { const $AssetsImagesDashBumperGen(); @@ -305,8 +413,11 @@ class $AssetsImagesDashBumperGen { class $AssetsImagesDinoAnimatronicGen { const $AssetsImagesDinoAnimatronicGen(); + /// File path: assets/images/dino/animatronic/head.png AssetGenImage get head => const AssetGenImage('assets/images/dino/animatronic/head.png'); + + /// File path: assets/images/dino/animatronic/mouth.png AssetGenImage get mouth => const AssetGenImage('assets/images/dino/animatronic/mouth.png'); } @@ -314,8 +425,11 @@ class $AssetsImagesDinoAnimatronicGen { class $AssetsImagesGoogleWordLetter1Gen { const $AssetsImagesGoogleWordLetter1Gen(); + /// File path: assets/images/google_word/letter1/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/google_word/letter1/dimmed.png'); + + /// File path: assets/images/google_word/letter1/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/google_word/letter1/lit.png'); } @@ -323,8 +437,11 @@ class $AssetsImagesGoogleWordLetter1Gen { class $AssetsImagesGoogleWordLetter2Gen { const $AssetsImagesGoogleWordLetter2Gen(); + /// File path: assets/images/google_word/letter2/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/google_word/letter2/dimmed.png'); + + /// File path: assets/images/google_word/letter2/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/google_word/letter2/lit.png'); } @@ -332,8 +449,11 @@ class $AssetsImagesGoogleWordLetter2Gen { class $AssetsImagesGoogleWordLetter3Gen { const $AssetsImagesGoogleWordLetter3Gen(); + /// File path: assets/images/google_word/letter3/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/google_word/letter3/dimmed.png'); + + /// File path: assets/images/google_word/letter3/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/google_word/letter3/lit.png'); } @@ -341,8 +461,11 @@ class $AssetsImagesGoogleWordLetter3Gen { class $AssetsImagesGoogleWordLetter4Gen { const $AssetsImagesGoogleWordLetter4Gen(); + /// File path: assets/images/google_word/letter4/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/google_word/letter4/dimmed.png'); + + /// File path: assets/images/google_word/letter4/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/google_word/letter4/lit.png'); } @@ -350,8 +473,11 @@ class $AssetsImagesGoogleWordLetter4Gen { class $AssetsImagesGoogleWordLetter5Gen { const $AssetsImagesGoogleWordLetter5Gen(); + /// File path: assets/images/google_word/letter5/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/google_word/letter5/dimmed.png'); + + /// File path: assets/images/google_word/letter5/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/google_word/letter5/lit.png'); } @@ -359,8 +485,11 @@ class $AssetsImagesGoogleWordLetter5Gen { class $AssetsImagesGoogleWordLetter6Gen { const $AssetsImagesGoogleWordLetter6Gen(); + /// File path: assets/images/google_word/letter6/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/google_word/letter6/dimmed.png'); + + /// File path: assets/images/google_word/letter6/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/google_word/letter6/lit.png'); } @@ -368,8 +497,11 @@ class $AssetsImagesGoogleWordLetter6Gen { class $AssetsImagesKickerLeftGen { const $AssetsImagesKickerLeftGen(); + /// File path: assets/images/kicker/left/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/kicker/left/dimmed.png'); + + /// File path: assets/images/kicker/left/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/kicker/left/lit.png'); } @@ -377,8 +509,11 @@ class $AssetsImagesKickerLeftGen { class $AssetsImagesKickerRightGen { const $AssetsImagesKickerRightGen(); + /// File path: assets/images/kicker/right/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/kicker/right/dimmed.png'); + + /// File path: assets/images/kicker/right/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/kicker/right/lit.png'); } @@ -386,8 +521,11 @@ class $AssetsImagesKickerRightGen { class $AssetsImagesMultiplierX2Gen { const $AssetsImagesMultiplierX2Gen(); + /// File path: assets/images/multiplier/x2/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/multiplier/x2/dimmed.png'); + + /// File path: assets/images/multiplier/x2/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/multiplier/x2/lit.png'); } @@ -395,8 +533,11 @@ class $AssetsImagesMultiplierX2Gen { class $AssetsImagesMultiplierX3Gen { const $AssetsImagesMultiplierX3Gen(); + /// File path: assets/images/multiplier/x3/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/multiplier/x3/dimmed.png'); + + /// File path: assets/images/multiplier/x3/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/multiplier/x3/lit.png'); } @@ -404,8 +545,11 @@ class $AssetsImagesMultiplierX3Gen { class $AssetsImagesMultiplierX4Gen { const $AssetsImagesMultiplierX4Gen(); + /// File path: assets/images/multiplier/x4/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/multiplier/x4/dimmed.png'); + + /// File path: assets/images/multiplier/x4/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/multiplier/x4/lit.png'); } @@ -413,8 +557,11 @@ class $AssetsImagesMultiplierX4Gen { class $AssetsImagesMultiplierX5Gen { const $AssetsImagesMultiplierX5Gen(); + /// File path: assets/images/multiplier/x5/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/multiplier/x5/dimmed.png'); + + /// File path: assets/images/multiplier/x5/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/multiplier/x5/lit.png'); } @@ -422,8 +569,11 @@ class $AssetsImagesMultiplierX5Gen { class $AssetsImagesMultiplierX6Gen { const $AssetsImagesMultiplierX6Gen(); + /// File path: assets/images/multiplier/x6/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/multiplier/x6/dimmed.png'); + + /// File path: assets/images/multiplier/x6/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/multiplier/x6/lit.png'); } @@ -439,10 +589,15 @@ class $AssetsImagesSparkyBumperGen { class $AssetsImagesSparkyComputerGen { const $AssetsImagesSparkyComputerGen(); + /// File path: assets/images/sparky/computer/base.png AssetGenImage get base => const AssetGenImage('assets/images/sparky/computer/base.png'); + + /// File path: assets/images/sparky/computer/glow.png AssetGenImage get glow => const AssetGenImage('assets/images/sparky/computer/glow.png'); + + /// File path: assets/images/sparky/computer/top.png AssetGenImage get top => const AssetGenImage('assets/images/sparky/computer/top.png'); } @@ -450,8 +605,11 @@ class $AssetsImagesSparkyComputerGen { class $AssetsImagesAndroidBumperAGen { const $AssetsImagesAndroidBumperAGen(); + /// File path: assets/images/android/bumper/a/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/android/bumper/a/dimmed.png'); + + /// File path: assets/images/android/bumper/a/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/android/bumper/a/lit.png'); } @@ -459,8 +617,11 @@ class $AssetsImagesAndroidBumperAGen { class $AssetsImagesAndroidBumperBGen { const $AssetsImagesAndroidBumperBGen(); + /// File path: assets/images/android/bumper/b/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/android/bumper/b/dimmed.png'); + + /// File path: assets/images/android/bumper/b/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/android/bumper/b/lit.png'); } @@ -468,8 +629,11 @@ class $AssetsImagesAndroidBumperBGen { class $AssetsImagesAndroidBumperCowGen { const $AssetsImagesAndroidBumperCowGen(); + /// File path: assets/images/android/bumper/cow/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/android/bumper/cow/dimmed.png'); + + /// File path: assets/images/android/bumper/cow/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/android/bumper/cow/lit.png'); } @@ -477,16 +641,27 @@ class $AssetsImagesAndroidBumperCowGen { class $AssetsImagesAndroidRampArrowGen { const $AssetsImagesAndroidRampArrowGen(); + /// File path: assets/images/android/ramp/arrow/active1.png AssetGenImage get active1 => const AssetGenImage('assets/images/android/ramp/arrow/active1.png'); + + /// File path: assets/images/android/ramp/arrow/active2.png AssetGenImage get active2 => const AssetGenImage('assets/images/android/ramp/arrow/active2.png'); + + /// File path: assets/images/android/ramp/arrow/active3.png AssetGenImage get active3 => const AssetGenImage('assets/images/android/ramp/arrow/active3.png'); + + /// File path: assets/images/android/ramp/arrow/active4.png AssetGenImage get active4 => const AssetGenImage('assets/images/android/ramp/arrow/active4.png'); + + /// File path: assets/images/android/ramp/arrow/active5.png AssetGenImage get active5 => const AssetGenImage('assets/images/android/ramp/arrow/active5.png'); + + /// File path: assets/images/android/ramp/arrow/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/android/ramp/arrow/inactive.png'); } @@ -494,8 +669,11 @@ class $AssetsImagesAndroidRampArrowGen { class $AssetsImagesDashBumperAGen { const $AssetsImagesDashBumperAGen(); + /// File path: assets/images/dash/bumper/a/active.png AssetGenImage get active => const AssetGenImage('assets/images/dash/bumper/a/active.png'); + + /// File path: assets/images/dash/bumper/a/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/dash/bumper/a/inactive.png'); } @@ -503,8 +681,11 @@ class $AssetsImagesDashBumperAGen { class $AssetsImagesDashBumperBGen { const $AssetsImagesDashBumperBGen(); + /// File path: assets/images/dash/bumper/b/active.png AssetGenImage get active => const AssetGenImage('assets/images/dash/bumper/b/active.png'); + + /// File path: assets/images/dash/bumper/b/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/dash/bumper/b/inactive.png'); } @@ -512,8 +693,11 @@ class $AssetsImagesDashBumperBGen { class $AssetsImagesDashBumperMainGen { const $AssetsImagesDashBumperMainGen(); + /// File path: assets/images/dash/bumper/main/active.png AssetGenImage get active => const AssetGenImage('assets/images/dash/bumper/main/active.png'); + + /// File path: assets/images/dash/bumper/main/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/dash/bumper/main/inactive.png'); } @@ -521,8 +705,11 @@ class $AssetsImagesDashBumperMainGen { class $AssetsImagesSparkyBumperAGen { const $AssetsImagesSparkyBumperAGen(); + /// File path: assets/images/sparky/bumper/a/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/sparky/bumper/a/dimmed.png'); + + /// File path: assets/images/sparky/bumper/a/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/sparky/bumper/a/lit.png'); } @@ -530,8 +717,11 @@ class $AssetsImagesSparkyBumperAGen { class $AssetsImagesSparkyBumperBGen { const $AssetsImagesSparkyBumperBGen(); + /// File path: assets/images/sparky/bumper/b/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/sparky/bumper/b/dimmed.png'); + + /// File path: assets/images/sparky/bumper/b/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/sparky/bumper/b/lit.png'); } @@ -539,8 +729,11 @@ class $AssetsImagesSparkyBumperBGen { class $AssetsImagesSparkyBumperCGen { const $AssetsImagesSparkyBumperCGen(); + /// File path: assets/images/sparky/bumper/c/dimmed.png AssetGenImage get dimmed => const AssetGenImage('assets/images/sparky/bumper/c/dimmed.png'); + + /// File path: assets/images/sparky/bumper/c/lit.png AssetGenImage get lit => const AssetGenImage('assets/images/sparky/bumper/c/lit.png'); } diff --git a/packages/pinball_components/lib/gen/fonts.gen.dart b/packages/pinball_components/lib/gen/fonts.gen.dart index b15f2dd0..5f77da16 100644 --- a/packages/pinball_components/lib/gen/fonts.gen.dart +++ b/packages/pinball_components/lib/gen/fonts.gen.dart @@ -3,9 +3,14 @@ /// FlutterGen /// ***************************************************** +// ignore_for_file: directives_ordering,unnecessary_import + class FontFamily { FontFamily._(); + /// Font family: PixeloidMono static const String pixeloidMono = 'PixeloidMono'; + + /// Font family: PixeloidSans static const String pixeloidSans = 'PixeloidSans'; } diff --git a/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart b/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart index edce2a78..5fd0c9fe 100644 --- a/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart +++ b/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart @@ -93,8 +93,6 @@ class AndroidBumper extends BodyComponent with InitialPosition, ZIndex { /// Creates an [AndroidBumper] without any children. /// /// This can be used for testing [AndroidBumper]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting AndroidBumper.test({ required this.bloc, @@ -105,9 +103,6 @@ class AndroidBumper extends BodyComponent with InitialPosition, ZIndex { final double _minorRadius; - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs final AndroidBumperCubit bloc; @override diff --git a/packages/pinball_components/lib/src/components/android_bumper/behaviors/android_bumper_ball_contact_behavior.dart b/packages/pinball_components/lib/src/components/android_bumper/behaviors/android_bumper_ball_contact_behavior.dart index d28aa39c..b89cf3cf 100644 --- a/packages/pinball_components/lib/src/components/android_bumper/behaviors/android_bumper_ball_contact_behavior.dart +++ b/packages/pinball_components/lib/src/components/android_bumper/behaviors/android_bumper_ball_contact_behavior.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; diff --git a/packages/pinball_components/lib/src/components/android_bumper/cubit/android_bumper_cubit.dart b/packages/pinball_components/lib/src/components/android_bumper/cubit/android_bumper_cubit.dart index 3e75f890..61ad9e5a 100644 --- a/packages/pinball_components/lib/src/components/android_bumper/cubit/android_bumper_cubit.dart +++ b/packages/pinball_components/lib/src/components/android_bumper/cubit/android_bumper_cubit.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:bloc/bloc.dart'; part 'android_bumper_state.dart'; diff --git a/packages/pinball_components/lib/src/components/android_bumper/cubit/android_bumper_state.dart b/packages/pinball_components/lib/src/components/android_bumper/cubit/android_bumper_state.dart index f101c3e9..a1177d23 100644 --- a/packages/pinball_components/lib/src/components/android_bumper/cubit/android_bumper_state.dart +++ b/packages/pinball_components/lib/src/components/android_bumper/cubit/android_bumper_state.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - part of 'android_bumper_cubit.dart'; enum AndroidBumperState { diff --git a/packages/pinball_components/lib/src/components/android_spaceship/android_spaceship.dart b/packages/pinball_components/lib/src/components/android_spaceship/android_spaceship.dart index d15a5516..0fd4628d 100644 --- a/packages/pinball_components/lib/src/components/android_spaceship/android_spaceship.dart +++ b/packages/pinball_components/lib/src/components/android_spaceship/android_spaceship.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:async'; import 'dart:math' as math; @@ -13,10 +11,8 @@ import 'package:pinball_flame/pinball_flame.dart'; export 'cubit/android_spaceship_cubit.dart'; class AndroidSpaceship extends Component { - AndroidSpaceship({ - required Vector2 position, - }) : bloc = AndroidSpaceshipCubit(), - super( + AndroidSpaceship({required Vector2 position}) + : super( children: [ _SpaceshipSaucer()..initialPosition = position, _SpaceshipSaucerSpriteAnimationComponent()..position = position, @@ -38,23 +34,10 @@ class AndroidSpaceship extends Component { /// Creates an [AndroidSpaceship] without any children. /// /// This can be used for testing [AndroidSpaceship]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting AndroidSpaceship.test({ - required this.bloc, Iterable? children, }) : super(children: children); - - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - final AndroidSpaceshipCubit bloc; - - @override - void onRemove() { - bloc.close(); - super.onRemove(); - } } class _SpaceshipSaucer extends BodyComponent with InitialPosition, Layered { @@ -129,7 +112,6 @@ class _SpaceshipSaucerSpriteAnimationComponent extends SpriteAnimationComponent } } -// TODO(allisonryan0002): add pulsing behavior. class _LightBeamSpriteComponent extends SpriteComponent with HasGameRef, ZIndex { _LightBeamSpriteComponent() diff --git a/packages/pinball_components/lib/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior.dart.dart b/packages/pinball_components/lib/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior.dart.dart index 58a8b3c3..b577b7b3 100644 --- a/packages/pinball_components/lib/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior.dart.dart +++ b/packages/pinball_components/lib/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior.dart.dart @@ -1,16 +1,18 @@ // ignore_for_file: public_member_api_docs +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; class AndroidSpaceshipEntranceBallContactBehavior - extends ContactBehavior { + extends ContactBehavior + with FlameBlocReader { @override void beginContact(Object other, Contact contact) { super.beginContact(other, contact); if (other is! Ball) return; - parent.parent.bloc.onBallEntered(); + bloc.onBallEntered(); } } diff --git a/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_cubit.dart b/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_cubit.dart index ad9de251..334c9cc3 100644 --- a/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_cubit.dart +++ b/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_cubit.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:bloc/bloc.dart'; part 'android_spaceship_state.dart'; diff --git a/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_state.dart b/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_state.dart index aae41c17..9375ebf2 100644 --- a/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_state.dart +++ b/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_state.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - part of 'android_spaceship_cubit.dart'; enum AndroidSpaceshipState { diff --git a/packages/pinball_components/lib/src/components/arcade_background/arcade_background.dart b/packages/pinball_components/lib/src/components/arcade_background/arcade_background.dart new file mode 100644 index 00000000..e9936367 --- /dev/null +++ b/packages/pinball_components/lib/src/components/arcade_background/arcade_background.dart @@ -0,0 +1,92 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:pinball_theme/pinball_theme.dart' as theme; + +export 'cubit/arcade_background_cubit.dart'; + +/// {@template arcade_background} +/// Background of the arcade that the pinball machine lives in. +/// {@endtemplate} +class ArcadeBackground extends Component with ZIndex { + /// {@macro arcade_background} + ArcadeBackground({String? assetPath}) + : this._( + bloc: ArcadeBackgroundCubit(), + assetPath: assetPath, + ); + + ArcadeBackground._({required this.bloc, String? assetPath}) + : super( + children: [ + FlameBlocProvider.value( + value: bloc, + children: [ArcadeBackgroundSpriteComponent(assetPath: assetPath)], + ) + ], + ) { + zIndex = ZIndexes.arcadeBackground; + } + + /// Creates an [ArcadeBackground] without any behaviors. + /// + /// This can be used for testing [ArcadeBackground]'s behaviors in isolation. + @visibleForTesting + ArcadeBackground.test({ + ArcadeBackgroundCubit? bloc, + String? assetPath, + }) : bloc = bloc ?? ArcadeBackgroundCubit(), + super( + children: [ + FlameBlocProvider.value( + value: bloc ?? ArcadeBackgroundCubit(), + children: [ArcadeBackgroundSpriteComponent(assetPath: assetPath)], + ) + ], + ); + + /// Bloc to update the arcade background sprite when a new character is + /// selected. + final ArcadeBackgroundCubit bloc; +} + +/// {@template arcade_background_sprite_component} +/// [SpriteComponent] for the [ArcadeBackground]. +/// {@endtemplate} +@visibleForTesting +class ArcadeBackgroundSpriteComponent extends SpriteComponent + with + FlameBlocListenable, + HasGameRef { + /// {@macro arcade_background_sprite_component} + ArcadeBackgroundSpriteComponent({required String? assetPath}) + : _assetPath = assetPath, + super( + anchor: Anchor.bottomCenter, + position: Vector2(0, 72.3), + ); + + final String? _assetPath; + + @override + void onNewState(ArcadeBackgroundState state) { + sprite = Sprite( + gameRef.images.fromCache(state.characterTheme.background.keyName), + ); + } + + @override + Future onLoad() async { + await super.onLoad(); + final sprite = Sprite( + gameRef.images + .fromCache(_assetPath ?? theme.Assets.images.dash.background.keyName), + ); + this.sprite = sprite; + size = sprite.originalSize / 10; + } +} diff --git a/packages/pinball_components/lib/src/components/arcade_background/cubit/arcade_background_cubit.dart b/packages/pinball_components/lib/src/components/arcade_background/cubit/arcade_background_cubit.dart new file mode 100644 index 00000000..5b2188bc --- /dev/null +++ b/packages/pinball_components/lib/src/components/arcade_background/cubit/arcade_background_cubit.dart @@ -0,0 +1,15 @@ +// ignore_for_file: public_member_api_docs + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +part 'arcade_background_state.dart'; + +class ArcadeBackgroundCubit extends Cubit { + ArcadeBackgroundCubit() : super(const ArcadeBackgroundState.initial()); + + void onCharacterSelected(CharacterTheme characterTheme) { + emit(ArcadeBackgroundState(characterTheme: characterTheme)); + } +} diff --git a/packages/pinball_components/lib/src/components/arcade_background/cubit/arcade_background_state.dart b/packages/pinball_components/lib/src/components/arcade_background/cubit/arcade_background_state.dart new file mode 100644 index 00000000..d2406105 --- /dev/null +++ b/packages/pinball_components/lib/src/components/arcade_background/cubit/arcade_background_state.dart @@ -0,0 +1,15 @@ +// ignore_for_file: public_member_api_docs + +part of 'arcade_background_cubit.dart'; + +class ArcadeBackgroundState extends Equatable { + const ArcadeBackgroundState({required this.characterTheme}); + + const ArcadeBackgroundState.initial() + : this(characterTheme: const DashTheme()); + + final CharacterTheme characterTheme; + + @override + List get props => [characterTheme]; +} diff --git a/packages/pinball_components/lib/src/components/ball/ball.dart b/packages/pinball_components/lib/src/components/ball/ball.dart index e8cea997..806e1905 100644 --- a/packages/pinball_components/lib/src/components/ball/ball.dart +++ b/packages/pinball_components/lib/src/components/ball/ball.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -8,27 +9,27 @@ import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; export 'behaviors/behaviors.dart'; +export 'cubit/ball_cubit.dart'; /// {@template ball} /// A solid, [BodyType.dynamic] sphere that rolls and bounces around. /// {@endtemplate} class Ball extends BodyComponent with Layered, InitialPosition, ZIndex { /// {@macro ball} - Ball({ - String? assetPath, - }) : super( + Ball({String? assetPath}) : this._(bloc: BallCubit(), assetPath: assetPath); + + Ball._({required this.bloc, String? assetPath}) + : super( renderBody: false, children: [ - _BallSpriteComponent(assetPath: assetPath), + FlameBlocProvider.value( + value: bloc, + children: [BallSpriteComponent(assetPath: assetPath)], + ), BallScalingBehavior(), BallGravitatingBehavior(), ], ) { - // TODO(ruimiguel): while developing Ball can be launched by clicking mouse, - // and default layer is Layer.all. But on final game Ball will be always be - // be launched from Plunger and LauncherRamp will modify it to Layer.board. - // We need to see what happens if Ball appears from other place like nest - // bumper, it will need to explicit change layer to Layer.board then. layer = Layer.board; } @@ -36,11 +37,22 @@ class Ball extends BodyComponent with Layered, InitialPosition, ZIndex { /// /// This can be used for testing [Ball]'s behaviors in isolation. @visibleForTesting - Ball.test() - : super( - children: [_BallSpriteComponent()], + Ball.test({ + BallCubit? bloc, + String? assetPath, + }) : bloc = bloc ?? BallCubit(), + super( + children: [ + FlameBlocProvider.value( + value: bloc ?? BallCubit(), + children: [BallSpriteComponent(assetPath: assetPath)], + ) + ], ); + /// Bloc to update the ball sprite when a new character is selected. + final BallCubit bloc; + /// The size of the [Ball]. static final Vector2 size = Vector2.all(4.13); @@ -56,11 +68,10 @@ class Ball extends BodyComponent with Layered, InitialPosition, ZIndex { return world.createBody(bodyDef)..createFixtureFromShape(shape, 1); } - /// Immediatly and completly [stop]s the ball. + /// Immediately and completely [stop]s the ball. /// /// The [Ball] will no longer be affected by any forces, including it's /// weight and those emitted from collisions. - // TODO(allisonryan0002): prevent motion from contact with other balls. void stop() { body ..gravityScale = Vector2.zero() @@ -76,21 +87,32 @@ class Ball extends BodyComponent with Layered, InitialPosition, ZIndex { } } -class _BallSpriteComponent extends SpriteComponent with HasGameRef { - _BallSpriteComponent({ - this.assetPath, - }) : super( - anchor: Anchor.center, - ); +/// {@template ball_sprite_component} +/// Visual representation of the [Ball]. +/// {@endtemplate} +@visibleForTesting +class BallSpriteComponent extends SpriteComponent + with HasGameRef, FlameBlocListenable { + /// {@macro ball_sprite_component} + BallSpriteComponent({required String? assetPath}) + : _assetPath = assetPath, + super(anchor: Anchor.center); + + final String? _assetPath; - final String? assetPath; + @override + void onNewState(BallState state) { + sprite = Sprite( + gameRef.images.fromCache(state.characterTheme.ball.keyName), + ); + } @override Future onLoad() async { await super.onLoad(); final sprite = Sprite( gameRef.images - .fromCache(assetPath ?? theme.Assets.images.dash.ball.keyName), + .fromCache(_assetPath ?? theme.Assets.images.dash.ball.keyName), ); this.sprite = sprite; size = sprite.originalSize / 12.5; diff --git a/packages/pinball_components/lib/src/components/ball/behaviors/ball_impulsing_behavior.dart b/packages/pinball_components/lib/src/components/ball/behaviors/ball_impulsing_behavior.dart new file mode 100644 index 00000000..d875ef7c --- /dev/null +++ b/packages/pinball_components/lib/src/components/ball/behaviors/ball_impulsing_behavior.dart @@ -0,0 +1,22 @@ +import 'package:flame/components.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// {@template ball_impulsing_behavior} +/// Impulses the [Ball] in a given direction. +/// {@endtemplate} +class BallImpulsingBehavior extends Component with ParentIsA { + /// {@macro ball_impulsing_behavior} + BallImpulsingBehavior({ + required Vector2 impulse, + }) : _impulse = impulse; + + final Vector2 _impulse; + + @override + Future onLoad() async { + await super.onLoad(); + parent.body.linearVelocity = _impulse; + shouldRemove = true; + } +} diff --git a/packages/pinball_components/lib/src/components/ball/behaviors/ball_scaling_behavior.dart b/packages/pinball_components/lib/src/components/ball/behaviors/ball_scaling_behavior.dart index 7fc06fb1..e1e7c405 100644 --- a/packages/pinball_components/lib/src/components/ball/behaviors/ball_scaling_behavior.dart +++ b/packages/pinball_components/lib/src/components/ball/behaviors/ball_scaling_behavior.dart @@ -16,9 +16,12 @@ class BallScalingBehavior extends Component with ParentIsA { parent.body.fixtures.first.shape.radius = (Ball.size.x / 2) * scaleFactor; - parent.firstChild()!.scale.setValues( - scaleFactor, - scaleFactor, - ); + final ballSprite = parent.descendants().whereType(); + if (ballSprite.isNotEmpty) { + ballSprite.single.scale.setValues( + scaleFactor, + scaleFactor, + ); + } } } diff --git a/packages/pinball_components/lib/src/components/ball/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/ball/behaviors/behaviors.dart index 1068a20e..d2be36a9 100644 --- a/packages/pinball_components/lib/src/components/ball/behaviors/behaviors.dart +++ b/packages/pinball_components/lib/src/components/ball/behaviors/behaviors.dart @@ -1,3 +1,4 @@ export 'ball_gravitating_behavior.dart'; +export 'ball_impulsing_behavior.dart'; export 'ball_scaling_behavior.dart'; export 'ball_turbo_charging_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/ball/cubit/ball_cubit.dart b/packages/pinball_components/lib/src/components/ball/cubit/ball_cubit.dart new file mode 100644 index 00000000..248a0e4f --- /dev/null +++ b/packages/pinball_components/lib/src/components/ball/cubit/ball_cubit.dart @@ -0,0 +1,13 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +part 'ball_state.dart'; + +class BallCubit extends Cubit { + BallCubit() : super(const BallState.initial()); + + void onCharacterSelected(CharacterTheme characterTheme) { + emit(BallState(characterTheme: characterTheme)); + } +} diff --git a/packages/pinball_components/lib/src/components/ball/cubit/ball_state.dart b/packages/pinball_components/lib/src/components/ball/cubit/ball_state.dart new file mode 100644 index 00000000..ea6009ab --- /dev/null +++ b/packages/pinball_components/lib/src/components/ball/cubit/ball_state.dart @@ -0,0 +1,12 @@ +part of 'ball_cubit.dart'; + +class BallState extends Equatable { + const BallState({required this.characterTheme}); + + const BallState.initial() : this(characterTheme: const DashTheme()); + + final CharacterTheme characterTheme; + + @override + List get props => [characterTheme]; +} diff --git a/packages/pinball_components/lib/src/components/baseboard.dart b/packages/pinball_components/lib/src/components/baseboard.dart index 47ba4666..2965d17f 100644 --- a/packages/pinball_components/lib/src/components/baseboard.dart +++ b/packages/pinball_components/lib/src/components/baseboard.dart @@ -3,6 +3,7 @@ import 'dart:math' as math; import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; /// {@template baseboard} /// Wing-shaped board piece to corral the [Ball] towards the [Flipper]s. diff --git a/packages/pinball_components/lib/src/components/board_background_sprite_component.dart b/packages/pinball_components/lib/src/components/board_background_sprite_component.dart index e42c2aca..f1e3354c 100644 --- a/packages/pinball_components/lib/src/components/board_background_sprite_component.dart +++ b/packages/pinball_components/lib/src/components/board_background_sprite_component.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame/components.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; diff --git a/packages/pinball_components/lib/src/components/board_dimensions.dart b/packages/pinball_components/lib/src/components/board_dimensions.dart index 3d547996..4f994f73 100644 --- a/packages/pinball_components/lib/src/components/board_dimensions.dart +++ b/packages/pinball_components/lib/src/components/board_dimensions.dart @@ -5,7 +5,6 @@ import 'package:flame/extensions.dart'; /// {@template board_dimensions} /// Contains various board properties and dimensions for global use. /// {@endtemplate} -// TODO(allisonryan0002): consider alternatives for global dimensions. class BoardDimensions { /// Width and height of the board. static final size = Vector2(101.6, 143.8); diff --git a/packages/pinball_components/lib/src/components/bumping_behavior.dart b/packages/pinball_components/lib/src/components/bumping_behavior.dart index af0d07c3..17931838 100644 --- a/packages/pinball_components/lib/src/components/bumping_behavior.dart +++ b/packages/pinball_components/lib/src/components/bumping_behavior.dart @@ -12,7 +12,7 @@ class BumpingBehavior extends ContactBehavior { /// Determines how strong the bump is. final double _strength; - /// This is used to recoginze the current state of a contact manifold in world + /// This is used to recognize the current state of a contact manifold in world /// coordinates. @visibleForTesting final WorldManifold worldManifold = WorldManifold(); diff --git a/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior.dart b/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior.dart index eff84ff4..a8435ec0 100644 --- a/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior.dart +++ b/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior.dart @@ -14,7 +14,7 @@ class ChromeDinoChompingBehavior extends ContactBehavior { super.beginContact(other, contact); if (other is! Ball) return; - other.firstChild()!.setOpacity(0); + other.descendants().whereType().single.setOpacity(0); parent.bloc.onChomp(other); } } diff --git a/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior.dart b/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior.dart index 78a8b9d5..876dd4d6 100644 --- a/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior.dart +++ b/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior.dart @@ -30,7 +30,7 @@ class ChromeDinoSpittingBehavior extends Component void _spit() { parent.bloc.state.ball! - ..firstChild()!.setOpacity(1) + ..descendants().whereType().single.setOpacity(1) ..body.linearVelocity = Vector2(-50, 0); parent.bloc.onSpit(); } diff --git a/packages/pinball_components/lib/src/components/chrome_dino/chrome_dino.dart b/packages/pinball_components/lib/src/components/chrome_dino/chrome_dino.dart index 61052b60..1de7b684 100644 --- a/packages/pinball_components/lib/src/components/chrome_dino/chrome_dino.dart +++ b/packages/pinball_components/lib/src/components/chrome_dino/chrome_dino.dart @@ -38,16 +38,11 @@ class ChromeDino extends BodyComponent /// Creates a [ChromeDino] without any children. /// /// This can be used for testing [ChromeDino]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting ChromeDino.test({ required this.bloc, }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs final ChromeDinoCubit bloc; /// Angle to rotate the dino up or down from the starting horizontal position. diff --git a/packages/pinball_components/lib/src/components/chrome_dino/cubit/chrome_dino_cubit.dart b/packages/pinball_components/lib/src/components/chrome_dino/cubit/chrome_dino_cubit.dart index 06e34199..551633e8 100644 --- a/packages/pinball_components/lib/src/components/chrome_dino/cubit/chrome_dino_cubit.dart +++ b/packages/pinball_components/lib/src/components/chrome_dino/cubit/chrome_dino_cubit.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:pinball_components/pinball_components.dart'; diff --git a/packages/pinball_components/lib/src/components/chrome_dino/cubit/chrome_dino_state.dart b/packages/pinball_components/lib/src/components/chrome_dino/cubit/chrome_dino_state.dart index 8ed6fa8c..c96d7406 100644 --- a/packages/pinball_components/lib/src/components/chrome_dino/cubit/chrome_dino_state.dart +++ b/packages/pinball_components/lib/src/components/chrome_dino/cubit/chrome_dino_state.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - part of 'chrome_dino_cubit.dart'; enum ChromeDinoStatus { diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index 54345772..75ba12dc 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -1,6 +1,7 @@ export 'android_animatronic.dart'; export 'android_bumper/android_bumper.dart'; export 'android_spaceship/android_spaceship.dart'; +export 'arcade_background/arcade_background.dart'; export 'ball/ball.dart'; export 'baseboard.dart'; export 'board_background_sprite_component.dart'; @@ -10,25 +11,22 @@ export 'boundaries.dart'; export 'camera_zoom.dart'; export 'chrome_dino/chrome_dino.dart'; export 'dash_animatronic.dart'; -export 'dash_nest_bumper/dash_nest_bumper.dart'; +export 'dash_bumper/dash_bumper.dart'; export 'dino_walls.dart'; export 'error_component.dart'; -export 'fire_effect.dart'; export 'flapper/flapper.dart'; -export 'flipper.dart'; +export 'flipper/flipper.dart'; export 'google_letter/google_letter.dart'; export 'initial_position.dart'; export 'joint_anchor.dart'; export 'kicker/kicker.dart'; export 'launch_ramp.dart'; -export 'layer.dart'; export 'layer_sensor/layer_sensor.dart'; export 'multiball/multiball.dart'; export 'multiplier/multiplier.dart'; export 'plunger.dart'; export 'rocket.dart'; -export 'score_component.dart'; -export 'shapes/shapes.dart'; +export 'score_component/score_component.dart'; export 'signpost/signpost.dart'; export 'skill_shot/skill_shot.dart'; export 'slingshot.dart'; diff --git a/packages/pinball_components/lib/src/components/dash_animatronic.dart b/packages/pinball_components/lib/src/components/dash_animatronic.dart index faa604e9..bb7d983b 100644 --- a/packages/pinball_components/lib/src/components/dash_animatronic.dart +++ b/packages/pinball_components/lib/src/components/dash_animatronic.dart @@ -2,7 +2,7 @@ import 'package:flame/components.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template dash_animatronic} -/// Animated Dash that sits on top of the [DashNestBumper.main]. +/// Animated Dash that sits on top of the [DashBumper.main]. /// {@endtemplate} class DashAnimatronic extends SpriteAnimationComponent with HasGameRef { /// {@macro dash_animatronic} diff --git a/packages/pinball_components/lib/src/components/dash_bumper/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/dash_bumper/behaviors/behaviors.dart new file mode 100644 index 00000000..0167887f --- /dev/null +++ b/packages/pinball_components/lib/src/components/dash_bumper/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'dash_bumper_ball_contact_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/dash_nest_bumper/behaviors/dash_nest_bumper_contact_behavior.dart b/packages/pinball_components/lib/src/components/dash_bumper/behaviors/dash_bumper_ball_contact_behavior.dart similarity index 72% rename from packages/pinball_components/lib/src/components/dash_nest_bumper/behaviors/dash_nest_bumper_contact_behavior.dart rename to packages/pinball_components/lib/src/components/dash_bumper/behaviors/dash_bumper_ball_contact_behavior.dart index 829229e4..d147515c 100644 --- a/packages/pinball_components/lib/src/components/dash_nest_bumper/behaviors/dash_nest_bumper_contact_behavior.dart +++ b/packages/pinball_components/lib/src/components/dash_bumper/behaviors/dash_bumper_ball_contact_behavior.dart @@ -1,11 +1,8 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -class DashNestBumperBallContactBehavior - extends ContactBehavior { +class DashBumperBallContactBehavior extends ContactBehavior { @override void beginContact(Object other, Contact contact) { super.beginContact(other, contact); diff --git a/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_cubit.dart b/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_cubit.dart new file mode 100644 index 00000000..84e626c4 --- /dev/null +++ b/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_cubit.dart @@ -0,0 +1,17 @@ +import 'package:bloc/bloc.dart'; + +part 'dash_bumper_state.dart'; + +class DashBumperCubit extends Cubit { + DashBumperCubit() : super(DashBumperState.inactive); + + /// Event added when the bumper contacts with a ball. + void onBallContacted() { + emit(DashBumperState.active); + } + + /// Event added when the bumper should return to its initial configuration. + void onReset() { + emit(DashBumperState.inactive); + } +} diff --git a/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_state.dart b/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_state.dart new file mode 100644 index 00000000..f15d2e57 --- /dev/null +++ b/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_state.dart @@ -0,0 +1,10 @@ +part of 'dash_bumper_cubit.dart'; + +/// Indicates the [DashBumperCubit]'s current state. +enum DashBumperState { + /// A lit up bumper. + active, + + /// A dimmed bumper. + inactive, +} diff --git a/packages/pinball_components/lib/src/components/dash_nest_bumper/dash_nest_bumper.dart b/packages/pinball_components/lib/src/components/dash_bumper/dash_bumper.dart similarity index 70% rename from packages/pinball_components/lib/src/components/dash_nest_bumper/dash_nest_bumper.dart rename to packages/pinball_components/lib/src/components/dash_bumper/dash_bumper.dart index 4495053d..1b960610 100644 --- a/packages/pinball_components/lib/src/components/dash_nest_bumper/dash_nest_bumper.dart +++ b/packages/pinball_components/lib/src/components/dash_bumper/dash_bumper.dart @@ -5,17 +5,17 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/src/components/bumping_behavior.dart'; -import 'package:pinball_components/src/components/dash_nest_bumper/behaviors/behaviors.dart'; +import 'package:pinball_components/src/components/dash_bumper/behaviors/behaviors.dart'; import 'package:pinball_flame/pinball_flame.dart'; -export 'cubit/dash_nest_bumper_cubit.dart'; +export 'cubit/dash_bumper_cubit.dart'; -/// {@template dash_nest_bumper} -/// Bumper with a nest appearance. +/// {@template dash_bumper} +/// Bumper for the flutter forest. /// {@endtemplate} -class DashNestBumper extends BodyComponent with InitialPosition { - /// {@macro dash_nest_bumper} - DashNestBumper._({ +class DashBumper extends BodyComponent with InitialPosition { + /// {@macro dash_bumper} + DashBumper._({ required double majorRadius, required double minorRadius, required String activeAssetPath, @@ -28,19 +28,22 @@ class DashNestBumper extends BodyComponent with InitialPosition { super( renderBody: false, children: [ - _DashNestBumperSpriteGroupComponent( + _DashBumperSpriteGroupComponent( activeAssetPath: activeAssetPath, inactiveAssetPath: inactiveAssetPath, position: spritePosition, current: bloc.state, ), - DashNestBumperBallContactBehavior(), + DashBumperBallContactBehavior(), ...?children, ], ); - /// {@macro dash_nest_bumper} - DashNestBumper.main({ + /// {@macro dash_bumper} + /// + /// [DashBumper.main], usually positioned with a [DashAnimatronic] on top of + /// it. + DashBumper.main({ Iterable? children, }) : this._( majorRadius: 5.1, @@ -48,15 +51,18 @@ class DashNestBumper extends BodyComponent with InitialPosition { activeAssetPath: Assets.images.dash.bumper.main.active.keyName, inactiveAssetPath: Assets.images.dash.bumper.main.inactive.keyName, spritePosition: Vector2(0, -0.3), - bloc: DashNestBumperCubit(), + bloc: DashBumperCubit(), children: [ ...?children, BumpingBehavior(strength: 20), ], ); - /// {@macro dash_nest_bumper} - DashNestBumper.a({ + /// {@macro dash_bumper} + /// + /// [DashBumper.a] is positioned at the right side of the [DashBumper.main] in + /// the flutter forest. + DashBumper.a({ Iterable? children, }) : this._( majorRadius: 3, @@ -64,15 +70,18 @@ class DashNestBumper extends BodyComponent with InitialPosition { activeAssetPath: Assets.images.dash.bumper.a.active.keyName, inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName, spritePosition: Vector2(0.3, -1.3), - bloc: DashNestBumperCubit(), + bloc: DashBumperCubit(), children: [ ...?children, BumpingBehavior(strength: 20), ], ); - /// {@macro dash_nest_bumper} - DashNestBumper.b({ + /// {@macro dash_bumper} + /// + /// [DashBumper.b] is positioned at the left side of the [DashBumper.main] in + /// the flutter forest. + DashBumper.b({ Iterable? children, }) : this._( majorRadius: 3.1, @@ -80,30 +89,26 @@ class DashNestBumper extends BodyComponent with InitialPosition { activeAssetPath: Assets.images.dash.bumper.b.active.keyName, inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName, spritePosition: Vector2(0.4, -1.2), - bloc: DashNestBumperCubit(), + bloc: DashBumperCubit(), children: [ ...?children, BumpingBehavior(strength: 20), ], ); - /// Creates an [DashNestBumper] without any children. + /// Creates a [DashBumper] without any children. /// - /// This can be used for testing [DashNestBumper]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 + /// This can be used for testing [DashBumper]'s behaviors in isolation. @visibleForTesting - DashNestBumper.test({required this.bloc}) + DashBumper.test({required this.bloc}) : _majorRadius = 3, _minorRadius = 2.5; final double _majorRadius; final double _minorRadius; - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs - final DashNestBumperCubit bloc; + final DashBumperCubit bloc; @override void onRemove() { @@ -126,14 +131,14 @@ class DashNestBumper extends BodyComponent with InitialPosition { } } -class _DashNestBumperSpriteGroupComponent - extends SpriteGroupComponent - with HasGameRef, ParentIsA { - _DashNestBumperSpriteGroupComponent({ +class _DashBumperSpriteGroupComponent + extends SpriteGroupComponent + with HasGameRef, ParentIsA { + _DashBumperSpriteGroupComponent({ required String activeAssetPath, required String inactiveAssetPath, required Vector2 position, - required DashNestBumperState current, + required DashBumperState current, }) : _activeAssetPath = activeAssetPath, _inactiveAssetPath = inactiveAssetPath, super( @@ -151,9 +156,9 @@ class _DashNestBumperSpriteGroupComponent parent.bloc.stream.listen((state) => current = state); final sprites = { - DashNestBumperState.active: + DashBumperState.active: Sprite(gameRef.images.fromCache(_activeAssetPath)), - DashNestBumperState.inactive: + DashBumperState.inactive: Sprite(gameRef.images.fromCache(_inactiveAssetPath)), }; this.sprites = sprites; diff --git a/packages/pinball_components/lib/src/components/dash_nest_bumper/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/dash_nest_bumper/behaviors/behaviors.dart deleted file mode 100644 index 839cbd67..00000000 --- a/packages/pinball_components/lib/src/components/dash_nest_bumper/behaviors/behaviors.dart +++ /dev/null @@ -1 +0,0 @@ -export 'dash_nest_bumper_contact_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/dash_nest_bumper/cubit/dash_nest_bumper_cubit.dart b/packages/pinball_components/lib/src/components/dash_nest_bumper/cubit/dash_nest_bumper_cubit.dart deleted file mode 100644 index 8fc6b157..00000000 --- a/packages/pinball_components/lib/src/components/dash_nest_bumper/cubit/dash_nest_bumper_cubit.dart +++ /dev/null @@ -1,19 +0,0 @@ -// ignore_for_file: public_member_api_docs - -import 'package:bloc/bloc.dart'; - -part 'dash_nest_bumper_state.dart'; - -class DashNestBumperCubit extends Cubit { - DashNestBumperCubit() : super(DashNestBumperState.inactive); - - /// Event added when the bumper contacts with a ball. - void onBallContacted() { - emit(DashNestBumperState.active); - } - - /// Event added when the bumper should return to its initial configuration. - void onReset() { - emit(DashNestBumperState.inactive); - } -} diff --git a/packages/pinball_components/lib/src/components/dash_nest_bumper/cubit/dash_nest_bumper_state.dart b/packages/pinball_components/lib/src/components/dash_nest_bumper/cubit/dash_nest_bumper_state.dart deleted file mode 100644 index c169069f..00000000 --- a/packages/pinball_components/lib/src/components/dash_nest_bumper/cubit/dash_nest_bumper_state.dart +++ /dev/null @@ -1,10 +0,0 @@ -part of 'dash_nest_bumper_cubit.dart'; - -/// Indicates the [DashNestBumperCubit]'s current state. -enum DashNestBumperState { - /// A lit up bumper. - active, - - /// A dimmed bumper. - inactive, -} diff --git a/packages/pinball_components/lib/src/components/fire_effect.dart b/packages/pinball_components/lib/src/components/fire_effect.dart deleted file mode 100644 index e793b3e6..00000000 --- a/packages/pinball_components/lib/src/components/fire_effect.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'dart:math' as math; - -import 'package:flame/components.dart'; -import 'package:flame/extensions.dart'; -import 'package:flame/particles.dart'; -import 'package:flame_forge2d/flame_forge2d.dart' hide Particle; -import 'package:flutter/material.dart'; - -const _particleRadius = 0.25; - -// TODO(erickzanardo): This component could just be a ParticleComponet, -/// unfortunately there is a Particle Component is not a PositionComponent, -/// which makes it hard to be used since we have camera transformations and on -// top of that, PositionComponent has a bug inside forge 2d games -/// -/// https://github.com/flame-engine/flame/issues/1484 -/// https://github.com/flame-engine/flame/issues/1484 - -/// {@template fire_effect} -/// A [BodyComponent] which creates a fire trail effect using the given -/// parameters -/// {@endtemplate} -class FireEffect extends ParticleSystemComponent { - /// {@macro fire_effect} - FireEffect({ - required this.burstPower, - required this.direction, - Vector2? position, - }) : super( - position: position, - ); - - /// A [double] value that will define how "strong" the burst of particles - /// will be. - final double burstPower; - - /// Which direction the burst will aim. - final Vector2 direction; - - @override - Future onLoad() async { - await super.onLoad(); - - final children = [ - ...List.generate(4, (index) { - return CircleParticle( - radius: _particleRadius, - paint: Paint()..color = Colors.yellow.darken((index + 1) / 4), - ); - }), - ...List.generate(4, (index) { - return CircleParticle( - radius: _particleRadius, - paint: Paint()..color = Colors.red.darken((index + 1) / 4), - ); - }), - ...List.generate(4, (index) { - return CircleParticle( - radius: _particleRadius, - paint: Paint()..color = Colors.orange.darken((index + 1) / 4), - ); - }), - ]; - final random = math.Random(); - final spreadTween = Tween(begin: -0.2, end: 0.2); - - particle = Particle.generate( - count: math.max((random.nextDouble() * (burstPower * 10)).toInt(), 1), - generator: (_) { - final spread = Vector2( - spreadTween.transform(random.nextDouble()), - spreadTween.transform(random.nextDouble()), - ); - final finalDirection = Vector2(direction.x, direction.y) + spread; - final speed = finalDirection * (burstPower * 20); - - return AcceleratedParticle( - lifespan: 5 / burstPower, - position: Vector2.zero(), - speed: speed, - child: children[random.nextInt(children.length)], - ); - }, - ); - } -} diff --git a/packages/pinball_components/lib/src/components/flapper/behaviors/flapper_spinning_behavior.dart b/packages/pinball_components/lib/src/components/flapper/behaviors/flapper_spinning_behavior.dart index 9a4e2a99..f6fea680 100644 --- a/packages/pinball_components/lib/src/components/flapper/behaviors/flapper_spinning_behavior.dart +++ b/packages/pinball_components/lib/src/components/flapper/behaviors/flapper_spinning_behavior.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; diff --git a/packages/pinball_components/lib/src/components/flipper/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/flipper/behaviors/behaviors.dart new file mode 100644 index 00000000..ef3630e7 --- /dev/null +++ b/packages/pinball_components/lib/src/components/flipper/behaviors/behaviors.dart @@ -0,0 +1,2 @@ +export 'flipper_jointing_behavior.dart'; +export 'flipper_key_controlling_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/flipper/behaviors/flipper_jointing_behavior.dart b/packages/pinball_components/lib/src/components/flipper/behaviors/flipper_jointing_behavior.dart new file mode 100644 index 00000000..8e487141 --- /dev/null +++ b/packages/pinball_components/lib/src/components/flipper/behaviors/flipper_jointing_behavior.dart @@ -0,0 +1,62 @@ +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// Joints the [Flipper] to allow pivoting around one end. +class FlipperJointingBehavior extends Component + with ParentIsA, HasGameRef { + @override + Future onLoad() async { + await super.onLoad(); + + final anchor = _FlipperAnchor(flipper: parent); + await add(anchor); + + final jointDef = _FlipperAnchorRevoluteJointDef( + flipper: parent, + anchor: anchor, + ); + parent.world.createJoint(RevoluteJoint(jointDef)); + } +} + +/// {@template flipper_anchor} +/// [JointAnchor] positioned at the end of a [Flipper]. +/// +/// The end of a [Flipper] depends on its [Flipper.side]. +/// {@endtemplate} +class _FlipperAnchor extends JointAnchor { + /// {@macro flipper_anchor} + _FlipperAnchor({ + required Flipper flipper, + }) { + initialPosition = Vector2( + (Flipper.size.x * flipper.side.direction) / 2 - + (1.65 * flipper.side.direction), + -0.15, + ); + } +} + +/// {@template flipper_anchor_revolute_joint_def} +/// Hinges one end of [Flipper] to a [_FlipperAnchor] to achieve a pivoting +/// motion. +/// {@endtemplate} +class _FlipperAnchorRevoluteJointDef extends RevoluteJointDef { + /// {@macro flipper_anchor_revolute_joint_def} + _FlipperAnchorRevoluteJointDef({ + required Flipper flipper, + required _FlipperAnchor anchor, + }) { + initialize( + flipper.body, + anchor.body, + flipper.body.position + anchor.body.position, + ); + + enableLimit = true; + upperAngle = 0.611; + lowerAngle = -upperAngle; + } +} diff --git a/lib/game/components/controlled_flipper.dart b/packages/pinball_components/lib/src/components/flipper/behaviors/flipper_key_controlling_behavior.dart similarity index 50% rename from lib/game/components/controlled_flipper.dart rename to packages/pinball_components/lib/src/components/flipper/behaviors/flipper_key_controlling_behavior.dart index 1d5502c6..95566e75 100644 --- a/lib/game/components/controlled_flipper.dart +++ b/packages/pinball_components/lib/src/components/flipper/behaviors/flipper_key_controlling_behavior.dart @@ -1,49 +1,33 @@ import 'package:flame/components.dart'; -import 'package:flame_bloc/flame_bloc.dart'; import 'package:flutter/services.dart'; -import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -/// {@template controlled_flipper} -/// A [Flipper] with a [FlipperController] attached. -/// {@endtemplate} -class ControlledFlipper extends Flipper with Controls { - /// {@macro controlled_flipper} - ControlledFlipper({ - required BoardSide side, - }) : super(side: side) { - controller = FlipperController(this); - } -} - -/// {@template flipper_controller} -/// A [ComponentController] that controls a [Flipper]s movement. -/// {@endtemplate} -class FlipperController extends ComponentController - with KeyboardHandler, FlameBlocReader { - /// {@macro flipper_controller} - FlipperController(Flipper flipper) - : _keys = flipper.side.flipperKeys, - super(flipper); - +/// Allows controlling the [Flipper]'s movement with keyboard input. +class FlipperKeyControllingBehavior extends Component + with KeyboardHandler, ParentIsA { /// The [LogicalKeyboardKey]s that will control the [Flipper]. /// /// [onKeyEvent] method listens to when one of these keys is pressed. - final List _keys; + late final List _keys; + + @override + Future onLoad() async { + await super.onLoad(); + _keys = parent.side.flipperKeys; + } @override bool onKeyEvent( RawKeyEvent event, Set keysPressed, ) { - if (!bloc.state.status.isPlaying) return true; if (!_keys.contains(event.logicalKey)) return true; if (event is RawKeyDownEvent) { - component.moveUp(); + parent.moveUp(); } else if (event is RawKeyUpEvent) { - component.moveDown(); + parent.moveDown(); } return false; diff --git a/packages/pinball_components/lib/src/components/flipper.dart b/packages/pinball_components/lib/src/components/flipper/flipper.dart similarity index 55% rename from packages/pinball_components/lib/src/components/flipper.dart rename to packages/pinball_components/lib/src/components/flipper/flipper.dart index b62d2390..280c157f 100644 --- a/packages/pinball_components/lib/src/components/flipper.dart +++ b/packages/pinball_components/lib/src/components/flipper/flipper.dart @@ -2,8 +2,11 @@ import 'dart:async'; import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/foundation.dart'; import 'package:pinball_components/pinball_components.dart'; +export 'behaviors/behaviors.dart'; + /// {@template flipper} /// A bat, typically found in pairs at the bottom of the board. /// @@ -15,9 +18,18 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition { required this.side, }) : super( renderBody: false, - children: [_FlipperSpriteComponent(side: side)], + children: [ + _FlipperSpriteComponent(side: side), + FlipperJointingBehavior(), + ], ); + /// Creates a [Flipper] without any children. + /// + /// This can be used for testing [Flipper]'s behaviors in isolation. + @visibleForTesting + Flipper.test({required this.side}); + /// The size of the [Flipper]. static final size = Vector2(13.5, 4.3); @@ -44,19 +56,6 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition { body.linearVelocity = Vector2(0, -_speed); } - /// Anchors the [Flipper] to the [RevoluteJoint] that controls its arc motion. - Future _anchorToJoint() async { - final anchor = _FlipperAnchor(flipper: this); - await add(anchor); - - final jointDef = _FlipperAnchorRevoluteJointDef( - flipper: this, - anchor: anchor, - ); - final joint = _FlipperJoint(jointDef); - world.createJoint(joint); - } - List _createFixtureDefs() { final direction = side.direction; @@ -73,7 +72,6 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition { assetShadow, 0, ); - final bigCircleFixtureDef = FixtureDef(bigCircleShape); final smallCircleShape = CircleShape()..radius = size.y * 0.23; smallCircleShape.position.setValues( @@ -82,7 +80,6 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition { assetShadow, 0, ); - final smallCircleFixtureDef = FixtureDef(smallCircleShape); final trapeziumVertices = side.isLeft ? [ @@ -98,26 +95,18 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition { Vector2(smallCircleShape.position.x, -smallCircleShape.radius), ]; final trapezium = PolygonShape()..set(trapeziumVertices); - final trapeziumFixtureDef = FixtureDef( - trapezium, - density: 50, // TODO(alestiago): Use a proper density. - friction: .1, // TODO(alestiago): Use a proper friction. - ); return [ - bigCircleFixtureDef, - smallCircleFixtureDef, - trapeziumFixtureDef, + FixtureDef(bigCircleShape), + FixtureDef(smallCircleShape), + FixtureDef( + trapezium, + density: 50, + friction: .1, + ), ]; } - @override - Future onLoad() async { - await super.onLoad(); - - await _anchorToJoint(); - } - @override Body createBody() { final bodyDef = BodyDef( @@ -131,15 +120,6 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition { return body; } - - @override - void onMount() { - super.onMount(); - - gameRef.ready().whenComplete( - () => body.joints.whereType<_FlipperJoint>().first.unlock(), - ); - } } class _FlipperSpriteComponent extends SpriteComponent with HasGameRef { @@ -163,73 +143,3 @@ class _FlipperSpriteComponent extends SpriteComponent with HasGameRef { size = sprite.originalSize / 10; } } - -/// {@template flipper_anchor} -/// [JointAnchor] positioned at the end of a [Flipper]. -/// -/// The end of a [Flipper] depends on its [Flipper.side]. -/// {@endtemplate} -class _FlipperAnchor extends JointAnchor { - /// {@macro flipper_anchor} - _FlipperAnchor({ - required Flipper flipper, - }) { - initialPosition = Vector2( - (Flipper.size.x * flipper.side.direction) / 2 - - (1.65 * flipper.side.direction), - -0.15, - ); - } -} - -/// {@template flipper_anchor_revolute_joint_def} -/// Hinges one end of [Flipper] to a [_FlipperAnchor] to achieve an arc motion. -/// {@endtemplate} -class _FlipperAnchorRevoluteJointDef extends RevoluteJointDef { - /// {@macro flipper_anchor_revolute_joint_def} - _FlipperAnchorRevoluteJointDef({ - required Flipper flipper, - required _FlipperAnchor anchor, - }) : side = flipper.side { - enableLimit = true; - initialize( - flipper.body, - anchor.body, - flipper.body.position + anchor.body.position, - ); - } - - final BoardSide side; -} - -/// {@template flipper_joint} -/// [RevoluteJoint] that controls the arc motion of a [Flipper]. -/// {@endtemplate} -class _FlipperJoint extends RevoluteJoint { - /// {@macro flipper_joint} - _FlipperJoint(_FlipperAnchorRevoluteJointDef def) - : side = def.side, - super(def) { - lock(); - } - - /// Half the angle of the arc motion. - static const _halfSweepingAngle = 0.611; - - final BoardSide side; - - /// Locks the [Flipper] to its resting position. - /// - /// The joint is locked when initialized in order to force the [Flipper] - /// at its resting position. - void lock() { - final angle = _halfSweepingAngle * side.direction; - setLimits(angle, angle); - } - - /// Unlocks the [Flipper] from its resting position. - void unlock() { - const angle = _halfSweepingAngle; - setLimits(-angle, angle); - } -} diff --git a/packages/pinball_components/lib/src/components/google_letter/behaviors/google_letter_ball_contact_behavior.dart b/packages/pinball_components/lib/src/components/google_letter/behaviors/google_letter_ball_contact_behavior.dart index c3f0423e..84a210ef 100644 --- a/packages/pinball_components/lib/src/components/google_letter/behaviors/google_letter_ball_contact_behavior.dart +++ b/packages/pinball_components/lib/src/components/google_letter/behaviors/google_letter_ball_contact_behavior.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; diff --git a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart b/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart index 9ef219ff..99b15702 100644 --- a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart +++ b/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:bloc/bloc.dart'; part 'google_letter_state.dart'; diff --git a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart b/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart index 1e5a29e8..12c7edd0 100644 --- a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart +++ b/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - part of 'google_letter_cubit.dart'; enum GoogleLetterState { diff --git a/packages/pinball_components/lib/src/components/google_letter/google_letter.dart b/packages/pinball_components/lib/src/components/google_letter/google_letter.dart index 16218fa3..9d678e30 100644 --- a/packages/pinball_components/lib/src/components/google_letter/google_letter.dart +++ b/packages/pinball_components/lib/src/components/google_letter/google_letter.dart @@ -68,16 +68,11 @@ class GoogleLetter extends BodyComponent with InitialPosition { /// Creates a [GoogleLetter] without any children. /// /// This can be used for testing [GoogleLetter]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting GoogleLetter.test({ required this.bloc, }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs final GoogleLetterCubit bloc; @override diff --git a/packages/pinball_components/lib/src/components/initial_position.dart b/packages/pinball_components/lib/src/components/initial_position.dart index 4265a3a7..1e9b6046 100644 --- a/packages/pinball_components/lib/src/components/initial_position.dart +++ b/packages/pinball_components/lib/src/components/initial_position.dart @@ -24,8 +24,6 @@ mixin InitialPosition on BodyComponent { @override Future onLoad() async { await super.onLoad(); - // TODO(alestiago): Investiagate why body.position.setFrom(initialPosition) - // works for some components and not others. assert( body.position == initialPosition, 'Body position does not match initialPosition.', diff --git a/packages/pinball_components/lib/src/components/joint_anchor.dart b/packages/pinball_components/lib/src/components/joint_anchor.dart index 63875d40..ceba41e3 100644 --- a/packages/pinball_components/lib/src/components/joint_anchor.dart +++ b/packages/pinball_components/lib/src/components/joint_anchor.dart @@ -12,7 +12,7 @@ import 'package:pinball_components/pinball_components.dart'; /// initialize( /// dynamicBody.body, /// anchor.body, -/// dynabmicBody.body + anchor.body.position, +/// dynamicBody.body + anchor.body.position, /// ); /// ``` /// {@endtemplate} diff --git a/packages/pinball_components/lib/src/components/kicker/behaviors/kicker_ball_contact_behavior.dart b/packages/pinball_components/lib/src/components/kicker/behaviors/kicker_ball_contact_behavior.dart index d5d2eb6c..d05f61ae 100644 --- a/packages/pinball_components/lib/src/components/kicker/behaviors/kicker_ball_contact_behavior.dart +++ b/packages/pinball_components/lib/src/components/kicker/behaviors/kicker_ball_contact_behavior.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; diff --git a/packages/pinball_components/lib/src/components/kicker/cubit/kicker_cubit.dart b/packages/pinball_components/lib/src/components/kicker/cubit/kicker_cubit.dart index 488f4683..23282a22 100644 --- a/packages/pinball_components/lib/src/components/kicker/cubit/kicker_cubit.dart +++ b/packages/pinball_components/lib/src/components/kicker/cubit/kicker_cubit.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:bloc/bloc.dart'; part 'kicker_state.dart'; diff --git a/packages/pinball_components/lib/src/components/kicker/cubit/kicker_state.dart b/packages/pinball_components/lib/src/components/kicker/cubit/kicker_state.dart index 08d52709..590eb3dd 100644 --- a/packages/pinball_components/lib/src/components/kicker/cubit/kicker_state.dart +++ b/packages/pinball_components/lib/src/components/kicker/cubit/kicker_state.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - part of 'kicker_cubit.dart'; enum KickerState { diff --git a/packages/pinball_components/lib/src/components/kicker/kicker.dart b/packages/pinball_components/lib/src/components/kicker/kicker.dart index 1a45ad60..611f6032 100644 --- a/packages/pinball_components/lib/src/components/kicker/kicker.dart +++ b/packages/pinball_components/lib/src/components/kicker/kicker.dart @@ -51,17 +51,12 @@ class Kicker extends BodyComponent with InitialPosition { /// Creates a [Kicker] without any children. /// /// This can be used for testing [Kicker]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting Kicker.test({ required this.bloc, required BoardSide side, }) : _side = side; - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs final KickerCubit bloc; @override @@ -129,7 +124,6 @@ class Kicker extends BodyComponent with InitialPosition { FixtureDef(bouncyEdge, userData: 'bouncy_edge'), ]; - // TODO(alestiago): Evaluate if there is value on centering the fixtures. final centroid = geometry.centroid( [ upperCircle.position + Vector2(0, -upperCircle.radius), @@ -177,9 +171,6 @@ class _KickerSpriteGroupComponent extends SpriteGroupComponent @override Future onLoad() async { await super.onLoad(); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs parent.bloc.stream.listen((state) => current = state); final sprites = { @@ -203,8 +194,6 @@ class _KickerSpriteGroupComponent extends SpriteGroupComponent } } -// TODO(alestiago): Evaluate if there's value on generalising this to -// all shapes. extension on Shape { void moveBy(Vector2 offset) { if (this is CircleShape) { diff --git a/packages/pinball_components/lib/src/components/launch_ramp.dart b/packages/pinball_components/lib/src/components/launch_ramp.dart index e8290cff..815c5d4c 100644 --- a/packages/pinball_components/lib/src/components/launch_ramp.dart +++ b/packages/pinball_components/lib/src/components/launch_ramp.dart @@ -32,9 +32,6 @@ class _LaunchRampBase extends BodyComponent with Layered, ZIndex { layer = Layer.launcher; } - // TODO(ruimiguel): final asset differs slightly from the current shape. We - // need to fix shape with correct vertices, but right now merge them to have - // final assets at game and not be blocked. List _createFixtureDefs() { final fixturesDef = []; diff --git a/packages/pinball_components/lib/src/components/layer_sensor/behaviors/layer_filtering_behavior.dart b/packages/pinball_components/lib/src/components/layer_sensor/behaviors/layer_filtering_behavior.dart index 06dca4b6..24148848 100644 --- a/packages/pinball_components/lib/src/components/layer_sensor/behaviors/layer_filtering_behavior.dart +++ b/packages/pinball_components/lib/src/components/layer_sensor/behaviors/layer_filtering_behavior.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; diff --git a/packages/pinball_components/lib/src/components/layer_sensor/layer_sensor.dart b/packages/pinball_components/lib/src/components/layer_sensor/layer_sensor.dart index 4b1d6ae3..dc43322c 100644 --- a/packages/pinball_components/lib/src/components/layer_sensor/layer_sensor.dart +++ b/packages/pinball_components/lib/src/components/layer_sensor/layer_sensor.dart @@ -1,8 +1,7 @@ -// ignore_for_file: avoid_renaming_method_parameters, public_member_api_docs - import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/src/components/layer_sensor/behaviors/layer_filtering_behavior.dart'; +import 'package:pinball_flame/pinball_flame.dart'; /// {@template layer_entrance_orientation} /// Determines if a layer entrance is oriented [up] or [down] on the board. @@ -50,8 +49,6 @@ abstract class LayerSensor extends BodyComponent with InitialPosition, Layered { Shape get shape; /// {@macro layer_entrance_orientation} - // TODO(ruimiguel): Try to remove the need of [LayerEntranceOrientation] for - // collision calculations. final LayerEntranceOrientation orientation; @override diff --git a/packages/pinball_components/lib/src/components/multiball/cubit/multiball_cubit.dart b/packages/pinball_components/lib/src/components/multiball/cubit/multiball_cubit.dart index 9d943c9d..78e3aeb8 100644 --- a/packages/pinball_components/lib/src/components/multiball/cubit/multiball_cubit.dart +++ b/packages/pinball_components/lib/src/components/multiball/cubit/multiball_cubit.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; diff --git a/packages/pinball_components/lib/src/components/multiball/cubit/multiball_state.dart b/packages/pinball_components/lib/src/components/multiball/cubit/multiball_state.dart index bbc66fd5..d1ce03d0 100644 --- a/packages/pinball_components/lib/src/components/multiball/cubit/multiball_state.dart +++ b/packages/pinball_components/lib/src/components/multiball/cubit/multiball_state.dart @@ -1,8 +1,5 @@ -// ignore_for_file: comment_references, public_member_api_docs - part of 'multiball_cubit.dart'; -/// Indicates the different sprite states for [MultiballSpriteGroupComponent]. enum MultiballLightState { lit, dimmed, diff --git a/packages/pinball_components/lib/src/components/multiball/multiball.dart b/packages/pinball_components/lib/src/components/multiball/multiball.dart index 663490ca..38150aea 100644 --- a/packages/pinball_components/lib/src/components/multiball/multiball.dart +++ b/packages/pinball_components/lib/src/components/multiball/multiball.dart @@ -75,16 +75,11 @@ class Multiball extends Component { /// Creates an [Multiball] without any children. /// /// This can be used for testing [Multiball]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting Multiball.test({ required this.bloc, }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs final MultiballCubit bloc; @override diff --git a/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_cubit.dart b/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_cubit.dart index 1d265b2e..370fc572 100644 --- a/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_cubit.dart +++ b/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_cubit.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:pinball_components/pinball_components.dart'; diff --git a/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_state.dart b/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_state.dart index e3adde70..4dc404b1 100644 --- a/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_state.dart +++ b/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_state.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - part of 'multiplier_cubit.dart'; enum MultiplierSpriteState { diff --git a/packages/pinball_components/lib/src/components/multiplier/multiplier.dart b/packages/pinball_components/lib/src/components/multiplier/multiplier.dart index 54d02857..64e64375 100644 --- a/packages/pinball_components/lib/src/components/multiplier/multiplier.dart +++ b/packages/pinball_components/lib/src/components/multiplier/multiplier.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame/components.dart'; import 'package:flutter/material.dart'; import 'package:pinball_components/gen/assets.gen.dart'; @@ -81,8 +79,6 @@ class Multiplier extends Component { /// Creates a [Multiplier] without any children. /// /// This can be used for testing [Multiplier]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting Multiplier.test({ required MultiplierValue value, @@ -91,8 +87,6 @@ class Multiplier extends Component { _position = Vector2.zero(), _angle = 0; -// TODO(ruimiguel): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 final MultiplierCubit bloc; final MultiplierValue _value; diff --git a/packages/pinball_components/lib/src/components/plunger.dart b/packages/pinball_components/lib/src/components/plunger.dart index 5b9b77b2..6f38eb37 100644 --- a/packages/pinball_components/lib/src/components/plunger.dart +++ b/packages/pinball_components/lib/src/components/plunger.dart @@ -6,7 +6,7 @@ import 'package:pinball_flame/pinball_flame.dart'; /// {@template plunger} /// [Plunger] serves as a spring, that shoots the ball on the right side of the -/// playfield. +/// play field. /// /// [Plunger] ignores gravity so the player controls its downward [pull]. /// {@endtemplate} @@ -178,26 +178,16 @@ class _PlungerSpriteAnimationGroupComponent @override Future onLoad() async { await super.onLoad(); - - // TODO(alestiago): Used cached images. final spriteSheet = await gameRef.images.load( Assets.images.plunger.plunger.keyName, ); - const amountPerRow = 20; const amountPerColumn = 1; - final textureSize = Vector2( spriteSheet.width / amountPerRow, spriteSheet.height / amountPerColumn, ); size = textureSize / 10; - - // TODO(ruimiguel): we only need plunger pull animation, and release is just - // to reverse it, so we need to divide by 2 while we don't have only half of - // the animation (but amountPerRow and amountPerColumn needs to be correct - // in order of calculate textureSize correctly). - final pullAnimation = SpriteAnimation.fromFrameData( spriteSheet, SpriteAnimationData.sequenced( @@ -209,7 +199,6 @@ class _PlungerSpriteAnimationGroupComponent loop: false, ), ); - animations = { _PlungerAnimationState.release: pullAnimation.reversed(), _PlungerAnimationState.pull: pullAnimation, diff --git a/packages/pinball_components/lib/src/components/score_component/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/score_component/behaviors/behaviors.dart new file mode 100644 index 00000000..dba073f0 --- /dev/null +++ b/packages/pinball_components/lib/src/components/score_component/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'score_component_scaling_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/score_component/behaviors/score_component_scaling_behavior.dart b/packages/pinball_components/lib/src/components/score_component/behaviors/score_component_scaling_behavior.dart new file mode 100644 index 00000000..5e3d184b --- /dev/null +++ b/packages/pinball_components/lib/src/components/score_component/behaviors/score_component_scaling_behavior.dart @@ -0,0 +1,24 @@ +import 'package:flame/components.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// Scales a [ScoreComponent] according to its position on the board. +class ScoreComponentScalingBehavior extends Component + with ParentIsA { + @override + void update(double dt) { + super.update(dt); + final boardHeight = BoardDimensions.bounds.height; + const maxShrinkValue = 0.83; + + final augmentedPosition = parent.position.y * 3; + final standardizedYPosition = augmentedPosition + (boardHeight / 2); + final scaleFactor = maxShrinkValue + + ((standardizedYPosition / boardHeight) * (1 - maxShrinkValue)); + + parent.scale.setValues( + scaleFactor, + scaleFactor, + ); + } +} diff --git a/packages/pinball_components/lib/src/components/score_component.dart b/packages/pinball_components/lib/src/components/score_component/score_component.dart similarity index 77% rename from packages/pinball_components/lib/src/components/score_component.dart rename to packages/pinball_components/lib/src/components/score_component/score_component.dart index 5f95878a..4908c337 100644 --- a/packages/pinball_components/lib/src/components/score_component.dart +++ b/packages/pinball_components/lib/src/components/score_component/score_component.dart @@ -1,10 +1,10 @@ -// ignore_for_file: public_member_api_docs - import 'dart:async'; import 'package:flame/components.dart'; import 'package:flame/effects.dart'; +import 'package:flutter/material.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/score_component/behaviors/score_component_scaling_behavior.dart'; import 'package:pinball_flame/pinball_flame.dart'; enum Points { @@ -28,10 +28,25 @@ class ScoreComponent extends SpriteComponent with HasGameRef, ZIndex { super( position: position, anchor: Anchor.center, + children: [ScoreComponentScalingBehavior()], ) { zIndex = ZIndexes.score; } + /// Creates a [ScoreComponent] without any children. + /// + /// This can be used for testing [ScoreComponent]'s behaviors in isolation. + @visibleForTesting + ScoreComponent.test({ + required this.points, + required Vector2 position, + required EffectController effectController, + }) : _effectController = effectController, + super( + position: position, + anchor: Anchor.center, + ); + late Points points; late final Effect _effect; diff --git a/packages/pinball_components/lib/src/components/signpost/cubit/signpost_cubit.dart b/packages/pinball_components/lib/src/components/signpost/cubit/signpost_cubit.dart index f94feebe..dc5bce9c 100644 --- a/packages/pinball_components/lib/src/components/signpost/cubit/signpost_cubit.dart +++ b/packages/pinball_components/lib/src/components/signpost/cubit/signpost_cubit.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:bloc/bloc.dart'; part 'signpost_state.dart'; diff --git a/packages/pinball_components/lib/src/components/signpost/cubit/signpost_state.dart b/packages/pinball_components/lib/src/components/signpost/cubit/signpost_state.dart index 72173bf1..3962207f 100644 --- a/packages/pinball_components/lib/src/components/signpost/cubit/signpost_state.dart +++ b/packages/pinball_components/lib/src/components/signpost/cubit/signpost_state.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - part of 'signpost_cubit.dart'; enum SignpostState { diff --git a/packages/pinball_components/lib/src/components/signpost/signpost.dart b/packages/pinball_components/lib/src/components/signpost/signpost.dart index d22f46f3..3ba486c1 100644 --- a/packages/pinball_components/lib/src/components/signpost/signpost.dart +++ b/packages/pinball_components/lib/src/components/signpost/signpost.dart @@ -9,7 +9,7 @@ export 'cubit/signpost_cubit.dart'; /// {@template signpost} /// A sign, found in the Flutter Forest. /// -/// Lights up a new sign whenever all three [DashNestBumper]s are hit. +/// Lights up a new sign whenever all three [DashBumper]s are hit. /// {@endtemplate} class Signpost extends BodyComponent with InitialPosition { /// {@macro signpost} @@ -36,16 +36,11 @@ class Signpost extends BodyComponent with InitialPosition { /// Creates a [Signpost] without any children. /// /// This can be used for testing [Signpost]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting Signpost.test({ required this.bloc, }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs final SignpostCubit bloc; @override diff --git a/packages/pinball_components/lib/src/components/skill_shot/behaviors/skill_shot_ball_contact_behavior.dart b/packages/pinball_components/lib/src/components/skill_shot/behaviors/skill_shot_ball_contact_behavior.dart index 62e4185f..0227acf9 100644 --- a/packages/pinball_components/lib/src/components/skill_shot/behaviors/skill_shot_ball_contact_behavior.dart +++ b/packages/pinball_components/lib/src/components/skill_shot/behaviors/skill_shot_ball_contact_behavior.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; diff --git a/packages/pinball_components/lib/src/components/skill_shot/cubit/skill_shot_cubit.dart b/packages/pinball_components/lib/src/components/skill_shot/cubit/skill_shot_cubit.dart index b9491385..453b2706 100644 --- a/packages/pinball_components/lib/src/components/skill_shot/cubit/skill_shot_cubit.dart +++ b/packages/pinball_components/lib/src/components/skill_shot/cubit/skill_shot_cubit.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; diff --git a/packages/pinball_components/lib/src/components/skill_shot/cubit/skill_shot_state.dart b/packages/pinball_components/lib/src/components/skill_shot/cubit/skill_shot_state.dart index 1e040db6..a1fdd424 100644 --- a/packages/pinball_components/lib/src/components/skill_shot/cubit/skill_shot_state.dart +++ b/packages/pinball_components/lib/src/components/skill_shot/cubit/skill_shot_state.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - part of 'skill_shot_cubit.dart'; enum SkillShotSpriteState { diff --git a/packages/pinball_components/lib/src/components/skill_shot/skill_shot.dart b/packages/pinball_components/lib/src/components/skill_shot/skill_shot.dart index 3bf10a7e..a3ea2af2 100644 --- a/packages/pinball_components/lib/src/components/skill_shot/skill_shot.dart +++ b/packages/pinball_components/lib/src/components/skill_shot/skill_shot.dart @@ -38,16 +38,11 @@ class SkillShot extends BodyComponent with ZIndex { /// Creates a [SkillShot] without any children. /// /// This can be used for testing [SkillShot]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting SkillShot.test({ required this.bloc, }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs final SkillShotCubit bloc; @override diff --git a/packages/pinball_components/lib/src/components/slingshot.dart b/packages/pinball_components/lib/src/components/slingshot.dart index 5d9e7849..2ebaff9b 100644 --- a/packages/pinball_components/lib/src/components/slingshot.dart +++ b/packages/pinball_components/lib/src/components/slingshot.dart @@ -37,7 +37,7 @@ class Slingshot extends BodyComponent with InitialPosition { }) : _angle = angle, super( children: [ - _SlinghsotSpriteComponent(spritePath, angle: angle), + _SlingshotSpriteComponent(spritePath, angle: angle), BumpingBehavior(strength: 20), ], renderBody: false, @@ -90,8 +90,8 @@ class Slingshot extends BodyComponent with InitialPosition { } } -class _SlinghsotSpriteComponent extends SpriteComponent with HasGameRef { - _SlinghsotSpriteComponent( +class _SlingshotSpriteComponent extends SpriteComponent with HasGameRef { + _SlingshotSpriteComponent( String path, { required double angle, }) : _path = path, diff --git a/packages/pinball_components/lib/src/components/spaceship_ramp/behavior/ramp_ball_ascending_contact_behavior.dart b/packages/pinball_components/lib/src/components/spaceship_ramp/behavior/ramp_ball_ascending_contact_behavior.dart index 2d0aad7c..db98a30a 100644 --- a/packages/pinball_components/lib/src/components/spaceship_ramp/behavior/ramp_ball_ascending_contact_behavior.dart +++ b/packages/pinball_components/lib/src/components/spaceship_ramp/behavior/ramp_ball_ascending_contact_behavior.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; @@ -11,7 +9,7 @@ import 'package:pinball_flame/pinball_flame.dart'; /// the [SpaceshipRamp]. /// {@endtemplate} class RampBallAscendingContactBehavior - extends ContactBehavior { + extends ContactBehavior { @override void beginContact(Object other, Contact contact) { super.beginContact(other, contact); diff --git a/packages/pinball_components/lib/src/components/spaceship_ramp/cubit/spaceship_ramp_cubit.dart b/packages/pinball_components/lib/src/components/spaceship_ramp/cubit/spaceship_ramp_cubit.dart index d27a7a2c..c3dc9e3e 100644 --- a/packages/pinball_components/lib/src/components/spaceship_ramp/cubit/spaceship_ramp_cubit.dart +++ b/packages/pinball_components/lib/src/components/spaceship_ramp/cubit/spaceship_ramp_cubit.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; diff --git a/packages/pinball_components/lib/src/components/spaceship_ramp/cubit/spaceship_ramp_state.dart b/packages/pinball_components/lib/src/components/spaceship_ramp/cubit/spaceship_ramp_state.dart index 7fae894f..2979f05f 100644 --- a/packages/pinball_components/lib/src/components/spaceship_ramp/cubit/spaceship_ramp_state.dart +++ b/packages/pinball_components/lib/src/components/spaceship_ramp/cubit/spaceship_ramp_state.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - part of 'spaceship_ramp_cubit.dart'; class SpaceshipRampState extends Equatable { diff --git a/packages/pinball_components/lib/src/components/spaceship_ramp/spaceship_ramp.dart b/packages/pinball_components/lib/src/components/spaceship_ramp/spaceship_ramp.dart index 0b407517..07a5e79b 100644 --- a/packages/pinball_components/lib/src/components/spaceship_ramp/spaceship_ramp.dart +++ b/packages/pinball_components/lib/src/components/spaceship_ramp/spaceship_ramp.dart @@ -27,19 +27,6 @@ class SpaceshipRamp extends Component { required this.bloc, }) : super( children: [ - // TODO(ruimiguel): refactor RampScoringSensor and - // _SpaceshipRampOpening to be in only one sensor if possible. - RampScoringSensor( - children: [ - RampBallAscendingContactBehavior(), - ], - )..initialPosition = Vector2(1.7, -20.4), - _SpaceshipRampOpening( - outsidePriority: ZIndexes.ballOnBoard, - rotation: math.pi, - ) - ..initialPosition = Vector2(1.7, -19.8) - ..layer = Layer.opening, _SpaceshipRampOpening( outsideLayer: Layer.spaceship, outsidePriority: ZIndexes.ballOnSpaceship, @@ -48,10 +35,9 @@ class SpaceshipRamp extends Component { ..initialPosition = Vector2(-13.7, -18.6) ..layer = Layer.spaceshipEntranceRamp, _SpaceshipRampBackground(), - _SpaceshipRampBoardOpeningSpriteComponent() - ..position = Vector2(3.4, -39.5), + SpaceshipRampBoardOpening()..initialPosition = Vector2(3.4, -39.5), _SpaceshipRampForegroundRailing(), - _SpaceshipRampBase()..initialPosition = Vector2(1.7, -20), + SpaceshipRampBase()..initialPosition = Vector2(3.4, -42.5), _SpaceshipRampBackgroundRailingSpriteComponent(), SpaceshipRampArrowSpriteComponent( current: bloc.state.hits, @@ -68,9 +54,6 @@ class SpaceshipRamp extends Component { required this.bloc, }) : super(); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs final SpaceshipRampCubit bloc; @override @@ -258,11 +241,115 @@ extension on SpaceshipRampArrowSpriteState { } } -class _SpaceshipRampBoardOpeningSpriteComponent extends SpriteComponent - with HasGameRef, ZIndex { - _SpaceshipRampBoardOpeningSpriteComponent() : super(anchor: Anchor.center) { +class SpaceshipRampBoardOpening extends BodyComponent + with Layered, ZIndex, InitialPosition, ParentIsA { + SpaceshipRampBoardOpening() + : super( + renderBody: false, + children: [ + _SpaceshipRampBoardOpeningSpriteComponent(), + RampBallAscendingContactBehavior()..applyTo(['inside']), + LayerContactBehavior(layer: Layer.spaceshipEntranceRamp) + ..applyTo(['inside']), + LayerContactBehavior( + layer: Layer.board, + onBegin: false, + )..applyTo(['outside']), + ZIndexContactBehavior( + zIndex: ZIndexes.ballOnBoard, + onBegin: false, + )..applyTo(['outside']), + ZIndexContactBehavior(zIndex: ZIndexes.ballOnSpaceshipRamp) + ..applyTo(['middle', 'inside']), + ], + ) { zIndex = ZIndexes.spaceshipRampBoardOpening; + layer = Layer.opening; + } + + /// Creates a [SpaceshipRampBoardOpening] without any children. + /// + /// This can be used for testing [SpaceshipRampBoardOpening]'s behaviors in + /// isolation. + @visibleForTesting + SpaceshipRampBoardOpening.test(); + + List _createFixtureDefs() { + final topEdge = EdgeShape() + ..set( + Vector2(-3.4, -1.2), + Vector2(3.4, -1.6), + ); + final bottomEdge = EdgeShape() + ..set( + Vector2(-6.2, 1.5), + Vector2(6.2, 1.5), + ); + final middleCurve = BezierCurveShape( + controlPoints: [ + topEdge.vertex1, + Vector2(0, 2.3), + Vector2(7.5, 2.3), + topEdge.vertex2, + ], + ); + final leftCurve = BezierCurveShape( + controlPoints: [ + Vector2(-4.4, -1.2), + Vector2(-4.65, 0), + bottomEdge.vertex1, + ], + ); + final rightCurve = BezierCurveShape( + controlPoints: [ + Vector2(4.4, -1.6), + Vector2(4.65, 0), + bottomEdge.vertex2, + ], + ); + + const outsideKey = 'outside'; + return [ + FixtureDef( + topEdge, + isSensor: true, + userData: 'inside', + ), + FixtureDef( + bottomEdge, + isSensor: true, + userData: outsideKey, + ), + FixtureDef( + middleCurve, + isSensor: true, + userData: 'middle', + ), + FixtureDef( + leftCurve, + isSensor: true, + userData: outsideKey, + ), + FixtureDef( + rightCurve, + isSensor: true, + userData: outsideKey, + ), + ]; + } + + @override + Body createBody() { + final bodyDef = BodyDef(position: initialPosition); + final body = world.createBody(bodyDef); + _createFixtureDefs().forEach(body.createFixture); + return body; } +} + +class _SpaceshipRampBoardOpeningSpriteComponent extends SpriteComponent + with HasGameRef { + _SpaceshipRampBoardOpeningSpriteComponent() : super(anchor: Anchor.center); @override Future onLoad() async { @@ -347,28 +434,33 @@ class _SpaceshipRampForegroundRailingSpriteComponent extends SpriteComponent } } -class _SpaceshipRampBase extends BodyComponent with InitialPosition, Layered { - _SpaceshipRampBase() : super(renderBody: false) { - layer = Layer.board; +@visibleForTesting +class SpaceshipRampBase extends BodyComponent + with InitialPosition, ContactCallbacks { + SpaceshipRampBase() : super(renderBody: false); + + @override + void preSolve(Object other, Contact contact, Manifold oldManifold) { + super.preSolve(other, contact, oldManifold); + if (other is! Layered) return; + // Although, the Layer should already be taking care of the contact + // filtering, this is to ensure the ball doesn't collide with the ramp base + // when the filtering is calculated on different time steps. + contact.setEnabled(other.layer == Layer.board); } @override Body createBody() { - const baseWidth = 9; - final baseShape = BezierCurveShape( + final shape = BezierCurveShape( controlPoints: [ - Vector2(initialPosition.x - baseWidth / 2, initialPosition.y), - Vector2(initialPosition.x - baseWidth / 2, initialPosition.y) + - Vector2(2, -5), - Vector2(initialPosition.x + baseWidth / 2, initialPosition.y) + - Vector2(-2, -5), - Vector2(initialPosition.x + baseWidth / 2, initialPosition.y) + Vector2(-4.25, 1.75), + Vector2(-2, -2.1), + Vector2(2, -2.3), + Vector2(4.1, 1.5), ], ); - final fixtureDef = FixtureDef(baseShape); - final bodyDef = BodyDef(position: initialPosition); - - return world.createBody(bodyDef)..createFixture(fixtureDef); + final bodyDef = BodyDef(position: initialPosition, userData: this); + return world.createBody(bodyDef)..createFixtureFromShape(shape); } } @@ -406,47 +498,3 @@ class _SpaceshipRampOpening extends LayerSensor { ); } } - -/// {@template ramp_scoring_sensor} -/// Small sensor body used to detect when a ball has entered the -/// [SpaceshipRamp]. -/// {@endtemplate} -class RampScoringSensor extends BodyComponent - with ParentIsA, InitialPosition, Layered { - /// {@macro ramp_scoring_sensor} - RampScoringSensor({ - Iterable? children, - }) : super( - children: children, - renderBody: false, - ) { - layer = Layer.spaceshipEntranceRamp; - } - - /// Creates a [RampScoringSensor] without any children. - /// - @visibleForTesting - RampScoringSensor.test(); - - @override - Body createBody() { - final shape = PolygonShape() - ..setAsBox( - 2.6, - .5, - initialPosition, - -5 * math.pi / 180, - ); - - final fixtureDef = FixtureDef( - shape, - isSensor: true, - ); - final bodyDef = BodyDef( - position: initialPosition, - userData: this, - ); - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } -} diff --git a/packages/pinball_components/lib/src/components/sparky_bumper/behaviors/sparky_bumper_ball_contact_behavior.dart b/packages/pinball_components/lib/src/components/sparky_bumper/behaviors/sparky_bumper_ball_contact_behavior.dart index 57db300c..bfdba4a0 100644 --- a/packages/pinball_components/lib/src/components/sparky_bumper/behaviors/sparky_bumper_ball_contact_behavior.dart +++ b/packages/pinball_components/lib/src/components/sparky_bumper/behaviors/sparky_bumper_ball_contact_behavior.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; diff --git a/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_cubit.dart b/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_cubit.dart index 2f7ba7c4..980aea43 100644 --- a/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_cubit.dart +++ b/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_cubit.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:bloc/bloc.dart'; part 'sparky_bumper_state.dart'; diff --git a/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_state.dart b/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_state.dart index 096af299..423d977c 100644 --- a/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_state.dart +++ b/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_state.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - part of 'sparky_bumper_cubit.dart'; enum SparkyBumperState { diff --git a/packages/pinball_components/lib/src/components/sparky_bumper/sparky_bumper.dart b/packages/pinball_components/lib/src/components/sparky_bumper/sparky_bumper.dart index b909f0ba..ed000201 100644 --- a/packages/pinball_components/lib/src/components/sparky_bumper/sparky_bumper.dart +++ b/packages/pinball_components/lib/src/components/sparky_bumper/sparky_bumper.dart @@ -11,7 +11,7 @@ import 'package:pinball_flame/pinball_flame.dart'; export 'cubit/sparky_bumper_cubit.dart'; /// {@template sparky_bumper} -/// Bumper for Sparky area. +/// Bumper for the Sparky Scorch. /// {@endtemplate} class SparkyBumper extends BodyComponent with InitialPosition, ZIndex { /// {@macro sparky_bumper} @@ -93,8 +93,6 @@ class SparkyBumper extends BodyComponent with InitialPosition, ZIndex { /// Creates an [SparkyBumper] without any children. /// /// This can be used for testing [SparkyBumper]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting SparkyBumper.test({ required this.bloc, @@ -104,9 +102,6 @@ class SparkyBumper extends BodyComponent with InitialPosition, ZIndex { final double _majorRadius; final double _minorRadius; - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs final SparkyBumperCubit bloc; @override @@ -152,9 +147,6 @@ class _SparkyBumperSpriteGroupComponent @override Future onLoad() async { await super.onLoad(); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs parent.bloc.stream.listen((state) => current = state); final sprites = { diff --git a/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_cubit.dart b/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_cubit.dart index e86defcd..49948961 100644 --- a/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_cubit.dart +++ b/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_cubit.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:bloc/bloc.dart'; part 'sparky_computer_state.dart'; diff --git a/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_state.dart b/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_state.dart index 372f1d15..0d5713b1 100644 --- a/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_state.dart +++ b/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_state.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - part of 'sparky_computer_cubit.dart'; enum SparkyComputerState { diff --git a/packages/pinball_components/lib/src/components/sparky_computer/sparky_computer.dart b/packages/pinball_components/lib/src/components/sparky_computer/sparky_computer.dart index 9025d69d..966eff02 100644 --- a/packages/pinball_components/lib/src/components/sparky_computer/sparky_computer.dart +++ b/packages/pinball_components/lib/src/components/sparky_computer/sparky_computer.dart @@ -31,17 +31,12 @@ class SparkyComputer extends BodyComponent { /// Creates a [SparkyComputer] without any children. /// /// This can be used for testing [SparkyComputer]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting SparkyComputer.test({ required this.bloc, Iterable? children, }) : super(children: children); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs final SparkyComputerCubit bloc; @override @@ -70,7 +65,7 @@ class SparkyComputer extends BodyComponent { ..setAsBox( 1, 0.1, - Vector2(-13.2, -49.9), + Vector2(-13.1, -49.7), -0.18, ); diff --git a/packages/pinball_components/lib/src/components/z_indexes.dart b/packages/pinball_components/lib/src/components/z_indexes.dart index 88447312..543ad7a7 100644 --- a/packages/pinball_components/lib/src/components/z_indexes.dart +++ b/packages/pinball_components/lib/src/components/z_indexes.dart @@ -1,7 +1,4 @@ -// ignore_for_file: public_member_api_docs - /// Z-Indexes for the component rendering order in the pinball game. -// TODO(allisonryan0002): find alternative to section comments. abstract class ZIndexes { static const _base = 0; static const _above = 1; @@ -21,8 +18,8 @@ abstract class ZIndexes { // Background - // TODO(allisonryan0002): fix this magic zindex. Could bump all priorities so - // there are no negatives. + static const arcadeBackground = _below + boardBackground; + static const boardBackground = 5 * _below + _base; static const decal = _above + boardBackground; @@ -111,7 +108,7 @@ abstract class ZIndexes { // Score - static const score = _above + spaceshipRampForegroundRailing; + static const score = _above + sparkyAnimatronic; // Debug information diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index 3fffaa88..3301a0fc 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: bloc: ^8.0.3 flame: ^1.1.1 + flame_bloc: ^1.4.0 flame_forge2d: git: url: https://github.com/flame-engine/flame @@ -16,8 +17,6 @@ dependencies: ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f flutter: sdk: flutter - geometry: - path: ../geometry intl: ^0.17.0 pinball_flame: path: ../pinball_flame @@ -91,6 +90,7 @@ flutter: - assets/images/multiplier/x6/ - assets/images/score/ - assets/images/backbox/ + - assets/images/backbox/button/ - assets/images/flapper/ - assets/images/skill_shot/ diff --git a/packages/pinball_components/sandbox/README.md b/packages/pinball_components/sandbox/README.md index 08509a8b..f0ff7f74 100644 --- a/packages/pinball_components/sandbox/README.md +++ b/packages/pinball_components/sandbox/README.md @@ -1,4 +1,4 @@ -# Sandbox +# Pinball Components Sandbox ![coverage][coverage_badge] [![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] @@ -6,7 +6,7 @@ Generated by the [Very Good CLI][very_good_cli_link] ๐Ÿค– -A sanbox application where components are showcased and developed in an isolated way +A sandbox application where components are showcased and developed in an isolated way --- diff --git a/packages/pinball_components/sandbox/lib/stories/android_acres/android_spaceship_game.dart b/packages/pinball_components/sandbox/lib/stories/android_acres/android_spaceship_game.dart index 976f4894..185f5351 100644 --- a/packages/pinball_components/sandbox/lib/stories/android_acres/android_spaceship_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/android_acres/android_spaceship_game.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flame/input.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; class AndroidSpaceshipGame extends BallGame { diff --git a/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_rail_game.dart b/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_rail_game.dart index 4093ad33..9acee409 100644 --- a/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_rail_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_rail_game.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flame/input.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; class SpaceshipRailGame extends BallGame { diff --git a/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_ramp_game.dart b/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_ramp_game.dart index fe4e6dae..1027002a 100644 --- a/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_ramp_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_ramp_game.dart @@ -4,6 +4,7 @@ import 'package:flame/input.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; class SpaceshipRampGame extends BallGame with KeyboardEvents { @@ -33,6 +34,9 @@ class SpaceshipRampGame extends BallGame with KeyboardEvents { - Press space to progress arrow sprites. '''; + @override + Color backgroundColor() => Colors.white; + late final SpaceshipRamp _spaceshipRamp; @override diff --git a/packages/pinball_components/sandbox/lib/stories/ball/basic_ball_game.dart b/packages/pinball_components/sandbox/lib/stories/ball/basic_ball_game.dart index f3ba50f3..b5e29a8e 100644 --- a/packages/pinball_components/sandbox/lib/stories/ball/basic_ball_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/ball/basic_ball_game.dart @@ -1,5 +1,6 @@ import 'package:flame/input.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; import 'package:sandbox/common/common.dart'; diff --git a/packages/pinball_components/sandbox/lib/stories/bottom_group/flipper_game.dart b/packages/pinball_components/sandbox/lib/stories/bottom_group/flipper_game.dart index 789fa8b4..bdb23141 100644 --- a/packages/pinball_components/sandbox/lib/stories/bottom_group/flipper_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/bottom_group/flipper_game.dart @@ -1,6 +1,4 @@ import 'package:flame/input.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; @@ -23,16 +21,6 @@ class FlipperGame extends BallGame with KeyboardEvents { - Press right arrow key or "D" to move the right flipper. '''; - static const _leftFlipperKeys = [ - LogicalKeyboardKey.arrowLeft, - LogicalKeyboardKey.keyA, - ]; - - static const _rightFlipperKeys = [ - LogicalKeyboardKey.arrowRight, - LogicalKeyboardKey.keyD, - ]; - late Flipper leftFlipper; late Flipper rightFlipper; @@ -50,32 +38,4 @@ class FlipperGame extends BallGame with KeyboardEvents { await traceAllBodies(); } - - @override - KeyEventResult onKeyEvent( - RawKeyEvent event, - Set keysPressed, - ) { - final movedLeftFlipper = _leftFlipperKeys.contains(event.logicalKey); - if (movedLeftFlipper) { - if (event is RawKeyDownEvent) { - leftFlipper.moveUp(); - } else if (event is RawKeyUpEvent) { - leftFlipper.moveDown(); - } - } - - final movedRightFlipper = _rightFlipperKeys.contains(event.logicalKey); - if (movedRightFlipper) { - if (event is RawKeyDownEvent) { - rightFlipper.moveUp(); - } else if (event is RawKeyUpEvent) { - rightFlipper.moveDown(); - } - } - - return movedLeftFlipper || movedRightFlipper - ? KeyEventResult.handled - : KeyEventResult.ignored; - } } diff --git a/packages/pinball_components/sandbox/lib/stories/effects/fire_effect_game.dart b/packages/pinball_components/sandbox/lib/stories/effects/fire_effect_game.dart deleted file mode 100644 index 3ca8ec1a..00000000 --- a/packages/pinball_components/sandbox/lib/stories/effects/fire_effect_game.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:sandbox/common/common.dart'; - -class FireEffectGame extends LineGame { - static const description = ''' - Shows how the FireEffect renders. - - - Drag a line to define the trail direction. -'''; - - @override - void onLine(Vector2 line) { - add(_EffectEmitter(line)); - } -} - -class _EffectEmitter extends Component { - _EffectEmitter(this.line) { - _direction = line.normalized(); - _force = line.length; - } - - static const _timerLimit = 2.0; - var _timer = _timerLimit; - - final Vector2 line; - - late Vector2 _direction; - late double _force; - - @override - void update(double dt) { - super.update(dt); - - if (_timer > 0) { - add( - FireEffect( - burstPower: (_timer / _timerLimit) * _force, - direction: _direction, - ), - ); - _timer -= dt; - } else { - removeFromParent(); - } - } -} diff --git a/packages/pinball_components/sandbox/lib/stories/effects/stories.dart b/packages/pinball_components/sandbox/lib/stories/effects/stories.dart index 9b022987..3ea8e06a 100644 --- a/packages/pinball_components/sandbox/lib/stories/effects/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/effects/stories.dart @@ -1,18 +1,11 @@ import 'package:dashbook/dashbook.dart'; import 'package:sandbox/common/common.dart'; import 'package:sandbox/stories/effects/camera_zoom_game.dart'; -import 'package:sandbox/stories/effects/fire_effect_game.dart'; void addEffectsStories(Dashbook dashbook) { - dashbook.storiesOf('Effects') - ..addGame( - title: 'Fire', - description: FireEffectGame.description, - gameBuilder: (_) => FireEffectGame(), - ) - ..addGame( - title: 'CameraZoom', - description: CameraZoomGame.description, - gameBuilder: (_) => CameraZoomGame(), - ); + dashbook.storiesOf('Effects').addGame( + title: 'CameraZoom', + description: CameraZoomGame.description, + gameBuilder: (_) => CameraZoomGame(), + ); } diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_a_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_a_game.dart similarity index 77% rename from packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_a_game.dart rename to packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_a_game.dart index 071f6aa1..d81540b0 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_a_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_a_game.dart @@ -4,8 +4,8 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; -class SmallDashNestBumperAGame extends BallGame { - SmallDashNestBumperAGame() +class DashBumperAGame extends BallGame { + DashBumperAGame() : super( imagesFileNames: [ Assets.images.dash.bumper.a.active.keyName, @@ -14,7 +14,7 @@ class SmallDashNestBumperAGame extends BallGame { ); static const description = ''' - Shows how a SmallDashNestBumper ("a") is rendered. + Shows how the "a" DashBumper is rendered. - Activate the "trace" parameter to overlay the body. '''; @@ -24,7 +24,7 @@ class SmallDashNestBumperAGame extends BallGame { await super.onLoad(); camera.followVector2(Vector2.zero()); - await add(DashNestBumper.a()..priority = 1); + await add(DashBumper.a()..priority = 1); await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_b_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_b_game.dart similarity index 77% rename from packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_b_game.dart rename to packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_b_game.dart index a47b9962..05664a3a 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_b_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_b_game.dart @@ -4,8 +4,8 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; -class SmallDashNestBumperBGame extends BallGame { - SmallDashNestBumperBGame() +class DashBumperBGame extends BallGame { + DashBumperBGame() : super( imagesFileNames: [ Assets.images.dash.bumper.b.active.keyName, @@ -14,7 +14,7 @@ class SmallDashNestBumperBGame extends BallGame { ); static const description = ''' - Shows how a SmallDashNestBumper ("b") is rendered. + Shows how the "b" DashBumper is rendered. - Activate the "trace" parameter to overlay the body. '''; @@ -24,7 +24,7 @@ class SmallDashNestBumperBGame extends BallGame { await super.onLoad(); camera.followVector2(Vector2.zero()); - await add(DashNestBumper.b()..priority = 1); + await add(DashBumper.b()..priority = 1); await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/big_dash_nest_bumper_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_main_game.dart similarity index 79% rename from packages/pinball_components/sandbox/lib/stories/flutter_forest/big_dash_nest_bumper_game.dart rename to packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_main_game.dart index 3580a175..6a927eb9 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/big_dash_nest_bumper_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_main_game.dart @@ -4,8 +4,8 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; -class BigDashNestBumperGame extends BallGame { - BigDashNestBumperGame() +class DashBumperMainGame extends BallGame { + DashBumperMainGame() : super( imagesFileNames: [ Assets.images.dash.bumper.main.active.keyName, @@ -14,7 +14,7 @@ class BigDashNestBumperGame extends BallGame { ); static const description = ''' - Shows how a BigDashNestBumper is rendered. + Shows how the "main" DashBumper is rendered. - Activate the "trace" parameter to overlay the body. '''; @@ -25,7 +25,7 @@ class BigDashNestBumperGame extends BallGame { camera.followVector2(Vector2.zero()); await add( - DashNestBumper.main()..priority = 1, + DashBumper.main()..priority = 1, ); await traceAllBodies(); } diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/stories.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/stories.dart index dd557a27..fcd64f79 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/stories.dart @@ -1,9 +1,9 @@ import 'package:dashbook/dashbook.dart'; import 'package:sandbox/common/common.dart'; -import 'package:sandbox/stories/flutter_forest/big_dash_nest_bumper_game.dart'; +import 'package:sandbox/stories/flutter_forest/dash_bumper_a_game.dart'; +import 'package:sandbox/stories/flutter_forest/dash_bumper_b_game.dart'; +import 'package:sandbox/stories/flutter_forest/dash_bumper_main_game.dart'; import 'package:sandbox/stories/flutter_forest/signpost_game.dart'; -import 'package:sandbox/stories/flutter_forest/small_dash_nest_bumper_a_game.dart'; -import 'package:sandbox/stories/flutter_forest/small_dash_nest_bumper_b_game.dart'; void addFlutterForestStories(Dashbook dashbook) { dashbook.storiesOf('Flutter Forest') @@ -13,18 +13,18 @@ void addFlutterForestStories(Dashbook dashbook) { gameBuilder: (_) => SignpostGame(), ) ..addGame( - title: 'Big Dash Nest Bumper', - description: BigDashNestBumperGame.description, - gameBuilder: (_) => BigDashNestBumperGame(), + title: 'Main Dash Bumper', + description: DashBumperMainGame.description, + gameBuilder: (_) => DashBumperMainGame(), ) ..addGame( - title: 'Small Dash Nest Bumper A', - description: SmallDashNestBumperAGame.description, - gameBuilder: (_) => SmallDashNestBumperAGame(), + title: 'Dash Bumper A', + description: DashBumperAGame.description, + gameBuilder: (_) => DashBumperAGame(), ) ..addGame( - title: 'Small Dash Nest Bumper B', - description: SmallDashNestBumperBGame.description, - gameBuilder: (_) => SmallDashNestBumperBGame(), + title: 'Dash Bumper B', + description: DashBumperBGame.description, + gameBuilder: (_) => DashBumperBGame(), ); } diff --git a/packages/pinball_components/sandbox/lib/stories/launch_ramp/launch_ramp_game.dart b/packages/pinball_components/sandbox/lib/stories/launch_ramp/launch_ramp_game.dart index b6955a26..c1d435d5 100644 --- a/packages/pinball_components/sandbox/lib/stories/launch_ramp/launch_ramp_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/launch_ramp/launch_ramp_game.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flame/input.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; class LaunchRampGame extends BallGame { diff --git a/packages/pinball_components/sandbox/lib/stories/layer/layer_game.dart b/packages/pinball_components/sandbox/lib/stories/layer/layer_game.dart index 40dcc824..808a43eb 100644 --- a/packages/pinball_components/sandbox/lib/stories/layer/layer_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/layer/layer_game.dart @@ -2,6 +2,7 @@ import 'package:flame/input.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; class LayerGame extends BallGame with TapDetector { diff --git a/packages/pinball_components/sandbox/pubspec.lock b/packages/pinball_components/sandbox/pubspec.lock index 0357656d..b5ac88b7 100644 --- a/packages/pinball_components/sandbox/pubspec.lock +++ b/packages/pinball_components/sandbox/pubspec.lock @@ -106,6 +106,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" + flame_bloc: + dependency: transitive + description: + name: flame_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" flame_forge2d: dependency: "direct main" description: @@ -120,6 +127,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: transitive + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "8.0.1" flutter_colorpicker: dependency: transitive description: @@ -214,6 +228,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" ordered_set: dependency: transitive description: @@ -298,6 +319,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.2.4" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.2" shared_preferences: dependency: transitive description: diff --git a/packages/pinball_components/test/src/components/android_bumper/android_bumper_test.dart b/packages/pinball_components/test/src/components/android_bumper/android_bumper_test.dart index aaca08fc..52e196dc 100644 --- a/packages/pinball_components/test/src/components/android_bumper/android_bumper_test.dart +++ b/packages/pinball_components/test/src/components/android_bumper/android_bumper_test.dart @@ -44,9 +44,6 @@ void main() { expect(game.contains(androidBumper), isTrue); }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockAndroidBumperCubit(); whenListen( diff --git a/packages/pinball_components/test/src/components/android_spaceship/android_spaceship_test.dart b/packages/pinball_components/test/src/components/android_spaceship/android_spaceship_test.dart index 1b672be4..70edd32e 100644 --- a/packages/pinball_components/test/src/components/android_spaceship/android_spaceship_test.dart +++ b/packages/pinball_components/test/src/components/android_spaceship/android_spaceship_test.dart @@ -1,7 +1,7 @@ // ignore_for_file: cascade_invocations -import 'package:bloc_test/bloc_test.dart'; import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -21,9 +21,18 @@ void main() { Assets.images.android.spaceship.lightBeam.keyName, ]; final flameTester = FlameTester(() => TestGame(assets)); + late AndroidSpaceshipCubit bloc; + + setUp(() { + bloc = _MockAndroidSpaceshipCubit(); + }); flameTester.test('loads correctly', (game) async { - final component = AndroidSpaceship(position: Vector2.zero()); + final component = + FlameBlocProvider.value( + value: bloc, + children: [AndroidSpaceship(position: Vector2.zero())], + ); await game.ensureAdd(component); expect(game.contains(component), isTrue); }); @@ -33,7 +42,13 @@ void main() { setUp: (game, tester) async { await game.images.loadAll(assets); final canvas = ZCanvasComponent( - children: [AndroidSpaceship(position: Vector2.zero())], + children: [ + FlameBlocProvider.value( + value: bloc, + children: [AndroidSpaceship(position: Vector2.zero())], + ), + ], ); await game.ensureAdd(canvas); game.camera.followVector2(Vector2.zero()); @@ -70,31 +85,16 @@ void main() { }, ); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs - flameTester.test('closes bloc when removed', (game) async { - final bloc = _MockAndroidSpaceshipCubit(); - whenListen( - bloc, - const Stream.empty(), - initialState: AndroidSpaceshipState.withoutBonus, - ); - when(bloc.close).thenAnswer((_) async {}); - final androidSpaceship = AndroidSpaceship.test(bloc: bloc); - - await game.ensureAdd(androidSpaceship); - game.remove(androidSpaceship); - await game.ready(); - - verify(bloc.close).called(1); - }); - flameTester.test( 'AndroidSpaceshipEntrance has an ' 'AndroidSpaceshipEntranceBallContactBehavior', (game) async { final androidSpaceship = AndroidSpaceship(position: Vector2.zero()); - await game.ensureAdd(androidSpaceship); + final provider = + FlameBlocProvider.value( + value: bloc, + children: [androidSpaceship], + ); + await game.ensureAdd(provider); final androidSpaceshipEntrance = androidSpaceship.firstChild(); diff --git a/packages/pinball_components/test/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior_test.dart b/packages/pinball_components/test/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior_test.dart index d6056beb..4b0f16ea 100644 --- a/packages/pinball_components/test/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior_test.dart +++ b/packages/pinball_components/test/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior_test.dart @@ -1,6 +1,7 @@ // ignore_for_file: cascade_invocations import 'package:bloc_test/bloc_test.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -43,16 +44,19 @@ void main() { ); final entrance = AndroidSpaceshipEntrance(); - final androidSpaceship = AndroidSpaceship.test( - bloc: bloc, - children: [entrance], + final androidSpaceship = FlameBlocProvider.value( + value: bloc, + children: [ + AndroidSpaceship.test(children: [entrance]) + ], ); await entrance.add(behavior); await game.ensureAdd(androidSpaceship); behavior.beginContact(_MockBall(), _MockContact()); - verify(androidSpaceship.bloc.onBallEntered).called(1); + verify(bloc.onBallEntered).called(1); }, ); }, diff --git a/packages/pinball_components/test/src/components/arcade_background/arcade_background_test.dart b/packages/pinball_components/test/src/components/arcade_background/arcade_background_test.dart new file mode 100644 index 00000000..a5c336d5 --- /dev/null +++ b/packages/pinball_components/test/src/components/arcade_background/arcade_background_test.dart @@ -0,0 +1,79 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_theme/pinball_theme.dart' as theme; + +import '../../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + theme.Assets.images.android.background.keyName, + theme.Assets.images.dash.background.keyName, + theme.Assets.images.dino.background.keyName, + theme.Assets.images.sparky.background.keyName, + ]; + + final flameTester = FlameTester(() => TestGame(assets)); + + group('ArcadeBackground', () { + test( + 'can be instantiated', + () { + expect(ArcadeBackground(), isA()); + expect(ArcadeBackground.test(), isA()); + }, + ); + + flameTester.test( + 'loads correctly', + (game) async { + final ball = ArcadeBackground(); + await game.ready(); + await game.ensureAdd(ball); + + expect(game.contains(ball), isTrue); + }, + ); + + flameTester.test( + 'has only one SpriteComponent', + (game) async { + final ball = ArcadeBackground(); + await game.ready(); + await game.ensureAdd(ball); + + expect( + ball.descendants().whereType().length, + equals(1), + ); + }, + ); + + flameTester.test( + 'ArcadeBackgroundSpriteComponent changes sprite onNewState', + (game) async { + final ball = ArcadeBackground(); + await game.ready(); + await game.ensureAdd(ball); + + final ballSprite = ball + .descendants() + .whereType() + .single; + final originalSprite = ballSprite.sprite; + + ballSprite.onNewState( + const ArcadeBackgroundState(characterTheme: theme.DinoTheme()), + ); + await game.ready(); + + final newSprite = ballSprite.sprite; + expect(newSprite != originalSprite, isTrue); + }, + ); + }); +} diff --git a/packages/pinball_components/test/src/components/arcade_background/cubit/arcade_background_cubit_test.dart b/packages/pinball_components/test/src/components/arcade_background/cubit/arcade_background_cubit_test.dart new file mode 100644 index 00000000..f2e99247 --- /dev/null +++ b/packages/pinball_components/test/src/components/arcade_background/cubit/arcade_background_cubit_test.dart @@ -0,0 +1,22 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +void main() { + group( + 'ArcadeBackgroundCubit', + () { + blocTest( + 'onCharacterSelected emits new theme', + build: ArcadeBackgroundCubit.new, + act: (bloc) => bloc.onCharacterSelected(const DinoTheme()), + expect: () => [ + const ArcadeBackgroundState( + characterTheme: DinoTheme(), + ), + ], + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/arcade_background/cubit/arcade_background_state_test.dart b/packages/pinball_components/test/src/components/arcade_background/cubit/arcade_background_state_test.dart new file mode 100644 index 00000000..97925fb6 --- /dev/null +++ b/packages/pinball_components/test/src/components/arcade_background/cubit/arcade_background_state_test.dart @@ -0,0 +1,32 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +void main() { + group('ArcadeBackgroundState', () { + test('supports value equality', () { + expect( + ArcadeBackgroundState(characterTheme: DashTheme()), + equals(ArcadeBackgroundState(characterTheme: DashTheme())), + ); + }); + + group('constructor', () { + test('can be instantiated', () { + expect( + ArcadeBackgroundState(characterTheme: DashTheme()), + isNotNull, + ); + }); + + test('initial contains DashTheme', () { + expect( + ArcadeBackgroundState.initial().characterTheme, + DashTheme(), + ); + }); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/ball/ball_test.dart b/packages/pinball_components/test/src/components/ball/ball_test.dart index 9195e0b2..9bf0bf27 100644 --- a/packages/pinball_components/test/src/components/ball/ball_test.dart +++ b/packages/pinball_components/test/src/components/ball/ball_test.dart @@ -1,9 +1,11 @@ // ignore_for_file: cascade_invocations +import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; import '../../../helpers/helpers.dart'; @@ -39,6 +41,41 @@ void main() { }, ); + flameTester.test( + 'has only one SpriteComponent', + (game) async { + final ball = Ball(); + await game.ready(); + await game.ensureAdd(ball); + + expect( + ball.descendants().whereType().length, + equals(1), + ); + }, + ); + + flameTester.test( + 'BallSpriteComponent changes sprite onNewState', + (game) async { + final ball = Ball(); + await game.ready(); + await game.ensureAdd(ball); + + final ballSprite = + ball.descendants().whereType().single; + final originalSprite = ballSprite.sprite; + + ballSprite.onNewState( + const BallState(characterTheme: theme.DinoTheme()), + ); + await game.ready(); + + final newSprite = ballSprite.sprite; + expect(newSprite != originalSprite, isTrue); + }, + ); + group('adds', () { flameTester.test('a BallScalingBehavior', (game) async { final ball = Ball(); diff --git a/packages/pinball_components/test/src/components/ball/behaviors/ball_implusing_behavior_test.dart b/packages/pinball_components/test/src/components/ball/behaviors/ball_implusing_behavior_test.dart new file mode 100644 index 00000000..53ab4553 --- /dev/null +++ b/packages/pinball_components/test/src/components/ball/behaviors/ball_implusing_behavior_test.dart @@ -0,0 +1,53 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_theme/pinball_theme.dart' as theme; + +import '../../../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group( + 'BallImpulsingBehavior', + () { + final asset = theme.Assets.images.dash.ball.keyName; + final flameTester = FlameTester(() => TestGame([asset])); + + test('can be instantiated', () { + expect( + BallImpulsingBehavior(impulse: Vector2.zero()), + isA(), + ); + }); + + flameTester.test( + 'impulses the ball with the given velocity when loaded ' + 'and then removes itself', + (game) async { + final ball = Ball.test(); + await game.ensureAdd(ball); + final impulse = Vector2.all(1); + final behavior = BallImpulsingBehavior(impulse: impulse); + await ball.ensureAdd(behavior); + + expect( + ball.body.linearVelocity.x, + equals(impulse.x), + ); + expect( + ball.body.linearVelocity.y, + equals(impulse.y), + ); + expect( + game.descendants().whereType().isEmpty, + isTrue, + ); + }, + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/ball/behaviors/ball_scaling_behavior_test.dart b/packages/pinball_components/test/src/components/ball/behaviors/ball_scaling_behavior_test.dart index bd0cca49..1fe84ae4 100644 --- a/packages/pinball_components/test/src/components/ball/behaviors/ball_scaling_behavior_test.dart +++ b/packages/pinball_components/test/src/components/ball/behaviors/ball_scaling_behavior_test.dart @@ -10,8 +10,9 @@ import '../../../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final asset = theme.Assets.images.dash.ball.keyName; - final flameTester = FlameTester(() => TestGame([asset])); + final flameTester = FlameTester( + () => TestGame([theme.Assets.images.dash.ball.keyName]), + ); group('BallScalingBehavior', () { test('can be instantiated', () { @@ -62,8 +63,8 @@ void main() { await game.ensureAddAll([ball1, ball2]); game.update(1); - final sprite1 = ball1.firstChild()!; - final sprite2 = ball2.firstChild()!; + final sprite1 = ball1.descendants().whereType().single; + final sprite2 = ball2.descendants().whereType().single; expect( sprite1.scale.x, greaterThan(sprite2.scale.x), diff --git a/packages/pinball_components/test/src/components/ball/cubit/ball_cubit_test.dart b/packages/pinball_components/test/src/components/ball/cubit/ball_cubit_test.dart new file mode 100644 index 00000000..c3a89120 --- /dev/null +++ b/packages/pinball_components/test/src/components/ball/cubit/ball_cubit_test.dart @@ -0,0 +1,18 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +void main() { + group( + 'BallCubit', + () { + blocTest( + 'onCharacterSelected emits new theme', + build: BallCubit.new, + act: (bloc) => bloc.onCharacterSelected(const DinoTheme()), + expect: () => [const BallState(characterTheme: DinoTheme())], + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/ball/cubit/ball_state_test.dart b/packages/pinball_components/test/src/components/ball/cubit/ball_state_test.dart new file mode 100644 index 00000000..1163ba1e --- /dev/null +++ b/packages/pinball_components/test/src/components/ball/cubit/ball_state_test.dart @@ -0,0 +1,22 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +void main() { + group('BallState', () { + test('supports value equality', () { + expect( + BallState(characterTheme: DashTheme()), + equals(const BallState(characterTheme: DashTheme())), + ); + }); + + group('constructor', () { + test('can be instantiated', () { + expect(const BallState(characterTheme: DashTheme()), isNotNull); + }); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/board_background_sprite_component_test.dart b/packages/pinball_components/test/src/components/board_background_sprite_component_test.dart index df35594f..79e8c56b 100644 --- a/packages/pinball_components/test/src/components/board_background_sprite_component_test.dart +++ b/packages/pinball_components/test/src/components/board_background_sprite_component_test.dart @@ -40,7 +40,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('golden/board-background.png'), + matchesGoldenFile('golden/board_background.png'), ); }, ); diff --git a/packages/pinball_components/test/src/components/bumping_behavior_test.dart b/packages/pinball_components/test/src/components/bumping_behavior_test.dart index dd0493f7..07e35cca 100644 --- a/packages/pinball_components/test/src/components/bumping_behavior_test.dart +++ b/packages/pinball_components/test/src/components/bumping_behavior_test.dart @@ -32,7 +32,7 @@ void main() { }); flameTester.testGameWidget( - 'the bump is greater when the strengh is greater', + 'the bump is greater when the strength is greater', setUp: (game, tester) async { final component1 = _TestBodyComponent(); final behavior1 = BumpingBehavior(strength: 1) diff --git a/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior_test.dart b/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior_test.dart index dfc33967..f5fd2b42 100644 --- a/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior_test.dart +++ b/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior_test.dart @@ -61,7 +61,10 @@ void main() { behavior.beginContact(ball, contact); - expect(ball.firstChild()!.getOpacity(), isZero); + expect( + ball.descendants().whereType().single.getOpacity(), + isZero, + ); verify(() => bloc.onChomp(ball)).called(1); }, diff --git a/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior_test.dart b/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior_test.dart index 8c2cbe57..0748040e 100644 --- a/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior_test.dart +++ b/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior_test.dart @@ -66,7 +66,14 @@ void main() { .timer .onTick!(); - expect(ball.firstChild()!.getOpacity(), equals(1)); + expect( + ball + .descendants() + .whereType() + .single + .getOpacity(), + equals(1), + ); expect(ball.body.linearVelocity, equals(Vector2(-50, 0))); }, ); diff --git a/packages/pinball_components/test/src/components/chrome_dino/chrome_dino_test.dart b/packages/pinball_components/test/src/components/chrome_dino/chrome_dino_test.dart index d6366092..a2e1c933 100644 --- a/packages/pinball_components/test/src/components/chrome_dino/chrome_dino_test.dart +++ b/packages/pinball_components/test/src/components/chrome_dino/chrome_dino_test.dart @@ -71,9 +71,6 @@ void main() { }, ); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockChromeDinoCubit(); whenListen( diff --git a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/down.png b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/down.png index a84d84c2..58180a63 100644 Binary files a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/down.png and b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/down.png differ diff --git a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/middle.png b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/middle.png index 0515f5f5..b6d31b1f 100644 Binary files a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/middle.png and b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/middle.png differ diff --git a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/up.png b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/up.png index 0a2d4674..48a834c0 100644 Binary files a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/up.png and b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/up.png differ diff --git a/packages/pinball_components/test/src/components/dash_nest_bumper/behaviors/dash_nest_bumper_ball_contact_behavior_test.dart b/packages/pinball_components/test/src/components/dash_nest_bumper/behaviors/dash_bumper_ball_contact_behavior_test.dart similarity index 55% rename from packages/pinball_components/test/src/components/dash_nest_bumper/behaviors/dash_nest_bumper_ball_contact_behavior_test.dart rename to packages/pinball_components/test/src/components/dash_nest_bumper/behaviors/dash_bumper_ball_contact_behavior_test.dart index 10627df6..3c8f51db 100644 --- a/packages/pinball_components/test/src/components/dash_nest_bumper/behaviors/dash_nest_bumper_ball_contact_behavior_test.dart +++ b/packages/pinball_components/test/src/components/dash_nest_bumper/behaviors/dash_bumper_ball_contact_behavior_test.dart @@ -6,11 +6,11 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_components/src/components/dash_nest_bumper/behaviors/behaviors.dart'; +import 'package:pinball_components/src/components/dash_bumper/behaviors/behaviors.dart'; import '../../../../helpers/helpers.dart'; -class _MockDashNestBumperCubit extends Mock implements DashNestBumperCubit {} +class _MockDashBumperCubit extends Mock implements DashBumperCubit {} class _MockBall extends Mock implements Ball {} @@ -21,33 +21,33 @@ void main() { final flameTester = FlameTester(TestGame.new); group( - 'DashNestBumperBallContactBehavior', + 'DashBumperBallContactBehavior', () { test('can be instantiated', () { expect( - DashNestBumperBallContactBehavior(), - isA(), + DashBumperBallContactBehavior(), + isA(), ); }); flameTester.test( 'beginContact emits onBallContacted when contacts with a ball', (game) async { - final behavior = DashNestBumperBallContactBehavior(); - final bloc = _MockDashNestBumperCubit(); + final behavior = DashBumperBallContactBehavior(); + final bloc = _MockDashBumperCubit(); whenListen( bloc, - const Stream.empty(), - initialState: DashNestBumperState.active, + const Stream.empty(), + initialState: DashBumperState.active, ); - final dashNestBumper = DashNestBumper.test(bloc: bloc); - await dashNestBumper.add(behavior); - await game.ensureAdd(dashNestBumper); + final bumper = DashBumper.test(bloc: bloc); + await bumper.add(behavior); + await game.ensureAdd(bumper); behavior.beginContact(_MockBall(), _MockContact()); - verify(dashNestBumper.bloc.onBallContacted).called(1); + verify(bumper.bloc.onBallContacted).called(1); }, ); }, diff --git a/packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_nest_bumper_cubit_test.dart b/packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumper_cubit_test.dart similarity index 53% rename from packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_nest_bumper_cubit_test.dart rename to packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumper_cubit_test.dart index 7e26bbf3..1b255cd5 100644 --- a/packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_nest_bumper_cubit_test.dart +++ b/packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumper_cubit_test.dart @@ -4,20 +4,20 @@ import 'package:pinball_components/pinball_components.dart'; void main() { group( - 'DashNestBumperCubit', + 'DashBumperCubit', () { - blocTest( + blocTest( 'onBallContacted emits active', - build: DashNestBumperCubit.new, + build: DashBumperCubit.new, act: (bloc) => bloc.onBallContacted(), - expect: () => [DashNestBumperState.active], + expect: () => [DashBumperState.active], ); - blocTest( + blocTest( 'onReset emits inactive', - build: DashNestBumperCubit.new, + build: DashBumperCubit.new, act: (bloc) => bloc.onReset(), - expect: () => [DashNestBumperState.inactive], + expect: () => [DashBumperState.inactive], ); }, ); diff --git a/packages/pinball_components/test/src/components/dash_nest_bumper/dash_bumper_test.dart b/packages/pinball_components/test/src/components/dash_nest_bumper/dash_bumper_test.dart new file mode 100644 index 00000000..a8ad8410 --- /dev/null +++ b/packages/pinball_components/test/src/components/dash_nest_bumper/dash_bumper_test.dart @@ -0,0 +1,139 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/bumping_behavior.dart'; +import 'package:pinball_components/src/components/dash_bumper/behaviors/behaviors.dart'; + +import '../../../helpers/helpers.dart'; + +class _MockDashBumperCubit extends Mock implements DashBumperCubit {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('DashBumper', () { + final flameTester = FlameTester( + () => TestGame( + [ + Assets.images.dash.bumper.main.active.keyName, + Assets.images.dash.bumper.main.inactive.keyName, + Assets.images.dash.bumper.a.active.keyName, + Assets.images.dash.bumper.a.inactive.keyName, + Assets.images.dash.bumper.b.active.keyName, + Assets.images.dash.bumper.b.inactive.keyName, + ], + ), + ); + + flameTester.test('"main" loads correctly', (game) async { + final bumper = DashBumper.main(); + await game.ensureAdd(bumper); + expect(game.contains(bumper), isTrue); + }); + + flameTester.test('"a" loads correctly', (game) async { + final bumper = DashBumper.a(); + await game.ensureAdd(bumper); + + expect(game.contains(bumper), isTrue); + }); + + flameTester.test('"b" loads correctly', (game) async { + final bumper = DashBumper.b(); + await game.ensureAdd(bumper); + expect(game.contains(bumper), isTrue); + }); + + // ignore: public_member_api_docs + flameTester.test('closes bloc when removed', (game) async { + final bloc = _MockDashBumperCubit(); + whenListen( + bloc, + const Stream.empty(), + initialState: DashBumperState.inactive, + ); + when(bloc.close).thenAnswer((_) async {}); + final bumper = DashBumper.test(bloc: bloc); + + await game.ensureAdd(bumper); + game.remove(bumper); + await game.ready(); + + verify(bloc.close).called(1); + }); + + flameTester.test('adds a bumperBallContactBehavior', (game) async { + final bumper = DashBumper.a(); + await game.ensureAdd(bumper); + expect( + bumper.children.whereType().single, + isNotNull, + ); + }); + + group("'main' adds", () { + flameTester.test('new children', (game) async { + final component = Component(); + final bumper = DashBumper.main( + children: [component], + ); + await game.ensureAdd(bumper); + expect(bumper.children, contains(component)); + }); + + flameTester.test('a BumpingBehavior', (game) async { + final bumper = DashBumper.main(); + await game.ensureAdd(bumper); + expect( + bumper.children.whereType().single, + isNotNull, + ); + }); + }); + + group("'a' adds", () { + flameTester.test('new children', (game) async { + final component = Component(); + final bumper = DashBumper.a( + children: [component], + ); + await game.ensureAdd(bumper); + expect(bumper.children, contains(component)); + }); + + flameTester.test('a BumpingBehavior', (game) async { + final bumper = DashBumper.a(); + await game.ensureAdd(bumper); + expect( + bumper.children.whereType().single, + isNotNull, + ); + }); + }); + + group("'b' adds", () { + flameTester.test('new children', (game) async { + final component = Component(); + final bumper = DashBumper.b( + children: [component], + ); + await game.ensureAdd(bumper); + expect(bumper.children, contains(component)); + }); + + flameTester.test('a BumpingBehavior', (game) async { + final bumper = DashBumper.b(); + await game.ensureAdd(bumper); + expect( + bumper.children.whereType().single, + isNotNull, + ); + }); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/dash_nest_bumper/dash_nest_bumper_test.dart b/packages/pinball_components/test/src/components/dash_nest_bumper/dash_nest_bumper_test.dart deleted file mode 100644 index 195231bf..00000000 --- a/packages/pinball_components/test/src/components/dash_nest_bumper/dash_nest_bumper_test.dart +++ /dev/null @@ -1,140 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:bloc_test/bloc_test.dart'; -import 'package:flame/components.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_components/src/components/bumping_behavior.dart'; -import 'package:pinball_components/src/components/dash_nest_bumper/behaviors/behaviors.dart'; - -import '../../../helpers/helpers.dart'; - -class _MockDashNestBumperCubit extends Mock implements DashNestBumperCubit {} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group('DashNestBumper', () { - final assets = [ - Assets.images.dash.bumper.main.active.keyName, - Assets.images.dash.bumper.main.inactive.keyName, - Assets.images.dash.bumper.a.active.keyName, - Assets.images.dash.bumper.a.inactive.keyName, - Assets.images.dash.bumper.b.active.keyName, - Assets.images.dash.bumper.b.inactive.keyName, - ]; - final flameTester = FlameTester(() => TestGame(assets)); - - flameTester.test('"main" loads correctly', (game) async { - final bumper = DashNestBumper.main(); - await game.ensureAdd(bumper); - expect(game.contains(bumper), isTrue); - }); - - flameTester.test('"a" loads correctly', (game) async { - final bumper = DashNestBumper.a(); - await game.ensureAdd(bumper); - - expect(game.contains(bumper), isTrue); - }); - - flameTester.test('"b" loads correctly', (game) async { - final bumper = DashNestBumper.b(); - await game.ensureAdd(bumper); - expect(game.contains(bumper), isTrue); - }); - - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs - flameTester.test('closes bloc when removed', (game) async { - final bloc = _MockDashNestBumperCubit(); - whenListen( - bloc, - const Stream.empty(), - initialState: DashNestBumperState.inactive, - ); - when(bloc.close).thenAnswer((_) async {}); - final dashNestBumper = DashNestBumper.test(bloc: bloc); - - await game.ensureAdd(dashNestBumper); - game.remove(dashNestBumper); - await game.ready(); - - verify(bloc.close).called(1); - }); - - flameTester.test('adds a DashNestBumperBallContactBehavior', (game) async { - final dashNestBumper = DashNestBumper.a(); - await game.ensureAdd(dashNestBumper); - expect( - dashNestBumper.children - .whereType() - .single, - isNotNull, - ); - }); - - group("'main' adds", () { - flameTester.test('new children', (game) async { - final component = Component(); - final dashNestBumper = DashNestBumper.main( - children: [component], - ); - await game.ensureAdd(dashNestBumper); - expect(dashNestBumper.children, contains(component)); - }); - - flameTester.test('a BumpingBehavior', (game) async { - final dashNestBumper = DashNestBumper.main(); - await game.ensureAdd(dashNestBumper); - expect( - dashNestBumper.children.whereType().single, - isNotNull, - ); - }); - }); - - group("'a' adds", () { - flameTester.test('new children', (game) async { - final component = Component(); - final dashNestBumper = DashNestBumper.a( - children: [component], - ); - await game.ensureAdd(dashNestBumper); - expect(dashNestBumper.children, contains(component)); - }); - - flameTester.test('a BumpingBehavior', (game) async { - final dashNestBumper = DashNestBumper.a(); - await game.ensureAdd(dashNestBumper); - expect( - dashNestBumper.children.whereType().single, - isNotNull, - ); - }); - }); - - group("'b' adds", () { - flameTester.test('new children', (game) async { - final component = Component(); - final dashNestBumper = DashNestBumper.b( - children: [component], - ); - await game.ensureAdd(dashNestBumper); - expect(dashNestBumper.children, contains(component)); - }); - - flameTester.test('a BumpingBehavior', (game) async { - final dashNestBumper = DashNestBumper.b(); - await game.ensureAdd(dashNestBumper); - expect( - dashNestBumper.children.whereType().single, - isNotNull, - ); - }); - }); - }); -} diff --git a/packages/pinball_components/test/src/components/dino_walls_test.dart b/packages/pinball_components/test/src/components/dino_walls_test.dart index 5e4471e5..dd8172ac 100644 --- a/packages/pinball_components/test/src/components/dino_walls_test.dart +++ b/packages/pinball_components/test/src/components/dino_walls_test.dart @@ -37,7 +37,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('golden/dino-walls.png'), + matchesGoldenFile('golden/dino_walls.png'), ); }, ); diff --git a/packages/pinball_components/test/src/components/fire_effect_test.dart b/packages/pinball_components/test/src/components/fire_effect_test.dart deleted file mode 100644 index 2c404747..00000000 --- a/packages/pinball_components/test/src/components/fire_effect_test.dart +++ /dev/null @@ -1,26 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:flame/components.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball_components/pinball_components.dart'; - -import '../../helpers/helpers.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(TestGame.new); - - flameTester.test( - 'loads correctly', - (game) async { - final fireEffect = FireEffect( - burstPower: 1, - direction: Vector2.zero(), - ); - await game.ensureAdd(fireEffect); - - expect(game.contains(fireEffect), isTrue); - }, - ); -} diff --git a/packages/pinball_components/test/src/components/flapper/flapper_test.dart b/packages/pinball_components/test/src/components/flapper/flapper_test.dart index 497bb5f6..c9be94f9 100644 --- a/packages/pinball_components/test/src/components/flapper/flapper_test.dart +++ b/packages/pinball_components/test/src/components/flapper/flapper_test.dart @@ -67,7 +67,7 @@ void main() { }, ); - flameTester.test('adds a FlapperSpiningBehavior to FlapperEntrance', + flameTester.test('adds a FlapperSpinningBehavior to FlapperEntrance', (game) async { final flapper = Flapper(); await game.ensureAdd(flapper); diff --git a/packages/pinball_components/test/src/components/flipper/behaviors/flipper_jointing_behavior_test.dart b/packages/pinball_components/test/src/components/flipper/behaviors/flipper_jointing_behavior_test.dart new file mode 100644 index 00000000..3d6c3b83 --- /dev/null +++ b/packages/pinball_components/test/src/components/flipper/behaviors/flipper_jointing_behavior_test.dart @@ -0,0 +1,38 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/src/components/components.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + group('FlipperJointingBehavior', () { + final flameTester = FlameTester(TestGame.new); + + test('can be instantiated', () { + expect( + FlipperJointingBehavior(), + isA(), + ); + }); + + flameTester.test('can be loaded', (game) async { + final behavior = FlipperJointingBehavior(); + final parent = Flipper.test(side: BoardSide.left); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + expect(parent.contains(behavior), isTrue); + }); + + flameTester.test('creates a joint', (game) async { + final behavior = FlipperJointingBehavior(); + final parent = Flipper.test(side: BoardSide.left); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + + expect(parent.body.joints, isNotEmpty); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/flipper/behaviors/flipper_key_controlling_behavior_test.dart b/packages/pinball_components/test/src/components/flipper/behaviors/flipper_key_controlling_behavior_test.dart new file mode 100644 index 00000000..11af6187 --- /dev/null +++ b/packages/pinball_components/test/src/components/flipper/behaviors/flipper_key_controlling_behavior_test.dart @@ -0,0 +1,357 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_test/flame_test.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../../../helpers/helpers.dart'; + +class _MockRawKeyDownEvent extends Mock implements RawKeyDownEvent { + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return super.toString(); + } +} + +class _MockRawKeyUpEvent extends Mock implements RawKeyUpEvent { + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return super.toString(); + } +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + group('FlipperKeyControllingBehavior', () { + final flameTester = FlameTester(TestGame.new); + + group( + 'onKeyEvent', + () { + late Flipper rightFlipper; + late Flipper leftFlipper; + + setUp(() { + rightFlipper = Flipper.test(side: BoardSide.right); + leftFlipper = Flipper.test(side: BoardSide.left); + }); + + group('on right Flipper', () { + flameTester.test( + 'moves upwards when right arrow is pressed', + (game) async { + await game.ensureAdd(rightFlipper); + final behavior = FlipperKeyControllingBehavior(); + await rightFlipper.ensureAdd(behavior); + + final event = _MockRawKeyDownEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.arrowRight, + ); + + behavior.onKeyEvent(event, {}); + + expect(rightFlipper.body.linearVelocity.y, isNegative); + expect(rightFlipper.body.linearVelocity.x, isZero); + }, + ); + + flameTester.test( + 'moves downwards when right arrow is released', + (game) async { + await game.ensureAdd(rightFlipper); + final behavior = FlipperKeyControllingBehavior(); + await rightFlipper.ensureAdd(behavior); + + final event = _MockRawKeyUpEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.arrowRight, + ); + + behavior.onKeyEvent(event, {}); + + expect(rightFlipper.body.linearVelocity.y, isPositive); + expect(rightFlipper.body.linearVelocity.x, isZero); + }, + ); + + flameTester.test( + 'moves upwards when D is pressed', + (game) async { + await game.ensureAdd(rightFlipper); + final behavior = FlipperKeyControllingBehavior(); + await rightFlipper.ensureAdd(behavior); + + final event = _MockRawKeyDownEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.keyD, + ); + + behavior.onKeyEvent(event, {}); + + expect(rightFlipper.body.linearVelocity.y, isNegative); + expect(rightFlipper.body.linearVelocity.x, isZero); + }, + ); + + flameTester.test( + 'moves downwards when D is released', + (game) async { + await game.ensureAdd(rightFlipper); + final behavior = FlipperKeyControllingBehavior(); + await rightFlipper.ensureAdd(behavior); + + final event = _MockRawKeyUpEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.keyD, + ); + + behavior.onKeyEvent(event, {}); + + expect(rightFlipper.body.linearVelocity.y, isPositive); + expect(rightFlipper.body.linearVelocity.x, isZero); + }, + ); + + group("doesn't move when", () { + flameTester.test( + 'left arrow is pressed', + (game) async { + await game.ensureAdd(rightFlipper); + final behavior = FlipperKeyControllingBehavior(); + await rightFlipper.ensureAdd(behavior); + + final event = _MockRawKeyDownEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.arrowLeft, + ); + + behavior.onKeyEvent(event, {}); + + expect(rightFlipper.body.linearVelocity.y, isZero); + expect(rightFlipper.body.linearVelocity.x, isZero); + }, + ); + + flameTester.test( + 'left arrow is released', + (game) async { + await game.ensureAdd(rightFlipper); + final behavior = FlipperKeyControllingBehavior(); + await rightFlipper.ensureAdd(behavior); + + final event = _MockRawKeyUpEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.arrowLeft, + ); + + behavior.onKeyEvent(event, {}); + + expect(rightFlipper.body.linearVelocity.y, isZero); + expect(rightFlipper.body.linearVelocity.x, isZero); + }, + ); + + flameTester.test( + 'A is pressed', + (game) async { + await game.ensureAdd(rightFlipper); + final behavior = FlipperKeyControllingBehavior(); + await rightFlipper.ensureAdd(behavior); + + final event = _MockRawKeyDownEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.keyA, + ); + + behavior.onKeyEvent(event, {}); + + expect(rightFlipper.body.linearVelocity.y, isZero); + expect(rightFlipper.body.linearVelocity.x, isZero); + }, + ); + + flameTester.test( + 'A is released', + (game) async { + await game.ensureAdd(rightFlipper); + final behavior = FlipperKeyControllingBehavior(); + await rightFlipper.ensureAdd(behavior); + + final event = _MockRawKeyUpEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.keyA, + ); + + behavior.onKeyEvent(event, {}); + + expect(rightFlipper.body.linearVelocity.y, isZero); + expect(rightFlipper.body.linearVelocity.x, isZero); + }, + ); + }); + }); + + group('on left Flipper', () { + flameTester.test( + 'moves upwards when left arrow is pressed', + (game) async { + await game.ensureAdd(leftFlipper); + final behavior = FlipperKeyControllingBehavior(); + await leftFlipper.ensureAdd(behavior); + + final event = _MockRawKeyDownEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.arrowLeft, + ); + + behavior.onKeyEvent(event, {}); + + expect(leftFlipper.body.linearVelocity.y, isNegative); + expect(leftFlipper.body.linearVelocity.x, isZero); + }, + ); + + flameTester.test( + 'moves downwards when left arrow is released', + (game) async { + await game.ensureAdd(leftFlipper); + final behavior = FlipperKeyControllingBehavior(); + await leftFlipper.ensureAdd(behavior); + + final event = _MockRawKeyUpEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.arrowLeft, + ); + + behavior.onKeyEvent(event, {}); + + expect(leftFlipper.body.linearVelocity.y, isPositive); + expect(leftFlipper.body.linearVelocity.x, isZero); + }, + ); + + flameTester.test( + 'moves upwards when A is pressed', + (game) async { + await game.ensureAdd(leftFlipper); + final behavior = FlipperKeyControllingBehavior(); + await leftFlipper.ensureAdd(behavior); + + final event = _MockRawKeyDownEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.keyA, + ); + + behavior.onKeyEvent(event, {}); + + expect(leftFlipper.body.linearVelocity.y, isNegative); + expect(leftFlipper.body.linearVelocity.x, isZero); + }, + ); + + flameTester.test( + 'moves downwards when A is released', + (game) async { + await game.ensureAdd(leftFlipper); + final behavior = FlipperKeyControllingBehavior(); + await leftFlipper.ensureAdd(behavior); + + final event = _MockRawKeyUpEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.keyA, + ); + + behavior.onKeyEvent(event, {}); + + expect(leftFlipper.body.linearVelocity.y, isPositive); + expect(leftFlipper.body.linearVelocity.x, isZero); + }, + ); + + group("doesn't move when", () { + flameTester.test( + 'right arrow is pressed', + (game) async { + await game.ensureAdd(leftFlipper); + final behavior = FlipperKeyControllingBehavior(); + await leftFlipper.ensureAdd(behavior); + + final event = _MockRawKeyDownEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.arrowRight, + ); + + behavior.onKeyEvent(event, {}); + + expect(leftFlipper.body.linearVelocity.y, isZero); + expect(leftFlipper.body.linearVelocity.x, isZero); + }, + ); + + flameTester.test( + 'right arrow is released', + (game) async { + await game.ensureAdd(leftFlipper); + final behavior = FlipperKeyControllingBehavior(); + await leftFlipper.ensureAdd(behavior); + + final event = _MockRawKeyUpEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.arrowRight, + ); + + behavior.onKeyEvent(event, {}); + + expect(leftFlipper.body.linearVelocity.y, isZero); + expect(leftFlipper.body.linearVelocity.x, isZero); + }, + ); + + flameTester.test( + 'D is pressed', + (game) async { + await game.ensureAdd(leftFlipper); + final behavior = FlipperKeyControllingBehavior(); + await leftFlipper.ensureAdd(behavior); + + final event = _MockRawKeyDownEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.keyD, + ); + + behavior.onKeyEvent(event, {}); + + expect(leftFlipper.body.linearVelocity.y, isZero); + expect(leftFlipper.body.linearVelocity.x, isZero); + }, + ); + + flameTester.test( + 'D is released', + (game) async { + await game.ensureAdd(leftFlipper); + final behavior = FlipperKeyControllingBehavior(); + await leftFlipper.ensureAdd(behavior); + + final event = _MockRawKeyUpEvent(); + when(() => event.logicalKey).thenReturn( + LogicalKeyboardKey.keyD, + ); + + behavior.onKeyEvent(event, {}); + + expect(leftFlipper.body.linearVelocity.y, isZero); + expect(leftFlipper.body.linearVelocity.x, isZero); + }, + ); + }); + }); + }, + ); + }); +} diff --git a/packages/pinball_components/test/src/components/flipper_test.dart b/packages/pinball_components/test/src/components/flipper/flipper_test.dart similarity index 96% rename from packages/pinball_components/test/src/components/flipper_test.dart rename to packages/pinball_components/test/src/components/flipper/flipper_test.dart index 314b1f77..4569f3ec 100644 --- a/packages/pinball_components/test/src/components/flipper_test.dart +++ b/packages/pinball_components/test/src/components/flipper/flipper_test.dart @@ -6,7 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; -import '../../helpers/helpers.dart'; +import '../../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -18,8 +18,6 @@ void main() { final flameTester = FlameTester(() => TestGame(assets)); group('Flipper', () { - // TODO(alestiago): Consider testing always both left and right Flipper. - flameTester.testGameWidget( 'renders correctly', setUp: (game, tester) async { @@ -38,7 +36,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('golden/flipper.png'), + matchesGoldenFile('../golden/flipper.png'), ); }, ); diff --git a/packages/pinball_components/test/src/components/golden/baseboard.png b/packages/pinball_components/test/src/components/golden/baseboard.png index 01141551..4b051dfa 100644 Binary files a/packages/pinball_components/test/src/components/golden/baseboard.png and b/packages/pinball_components/test/src/components/golden/baseboard.png differ diff --git a/packages/pinball_components/test/src/components/golden/board-background.png b/packages/pinball_components/test/src/components/golden/board-background.png deleted file mode 100644 index 31abceb1..00000000 Binary files a/packages/pinball_components/test/src/components/golden/board-background.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/board_background.png b/packages/pinball_components/test/src/components/golden/board_background.png new file mode 100644 index 00000000..c15a29ce Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/board_background.png differ diff --git a/packages/pinball_components/test/src/components/golden/boundaries.png b/packages/pinball_components/test/src/components/golden/boundaries.png index 68f57a86..ab6716dd 100644 Binary files a/packages/pinball_components/test/src/components/golden/boundaries.png and b/packages/pinball_components/test/src/components/golden/boundaries.png differ diff --git a/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png b/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png index 035a152f..f31d05e0 100644 Binary files a/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png and b/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png differ diff --git a/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png b/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png index 23c1142d..f2074da8 100644 Binary files a/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png and b/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png differ diff --git a/packages/pinball_components/test/src/components/golden/camera_zoom/no_zoom.png b/packages/pinball_components/test/src/components/golden/camera_zoom/no_zoom.png index 200ab49f..2975ac0d 100644 Binary files a/packages/pinball_components/test/src/components/golden/camera_zoom/no_zoom.png and b/packages/pinball_components/test/src/components/golden/camera_zoom/no_zoom.png differ diff --git a/packages/pinball_components/test/src/components/golden/dash_animatronic/end.png b/packages/pinball_components/test/src/components/golden/dash_animatronic/end.png index c8218fe1..3ee917ee 100644 Binary files a/packages/pinball_components/test/src/components/golden/dash_animatronic/end.png and b/packages/pinball_components/test/src/components/golden/dash_animatronic/end.png differ diff --git a/packages/pinball_components/test/src/components/golden/dash_animatronic/middle.png b/packages/pinball_components/test/src/components/golden/dash_animatronic/middle.png index 9e79695a..e57d0f07 100644 Binary files a/packages/pinball_components/test/src/components/golden/dash_animatronic/middle.png and b/packages/pinball_components/test/src/components/golden/dash_animatronic/middle.png differ diff --git a/packages/pinball_components/test/src/components/golden/dash_animatronic/start.png b/packages/pinball_components/test/src/components/golden/dash_animatronic/start.png index 3e5e91f5..5a16627d 100644 Binary files a/packages/pinball_components/test/src/components/golden/dash_animatronic/start.png and b/packages/pinball_components/test/src/components/golden/dash_animatronic/start.png differ diff --git a/packages/pinball_components/test/src/components/golden/dino-walls.png b/packages/pinball_components/test/src/components/golden/dino-walls.png deleted file mode 100644 index 31b317c1..00000000 Binary files a/packages/pinball_components/test/src/components/golden/dino-walls.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/dino_walls.png b/packages/pinball_components/test/src/components/golden/dino_walls.png new file mode 100644 index 00000000..1a50449d Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/dino_walls.png differ diff --git a/packages/pinball_components/test/src/components/golden/flipper.png b/packages/pinball_components/test/src/components/golden/flipper.png index 07fe81ed..8507618d 100644 Binary files a/packages/pinball_components/test/src/components/golden/flipper.png and b/packages/pinball_components/test/src/components/golden/flipper.png differ diff --git a/packages/pinball_components/test/src/components/golden/launch-ramp.png b/packages/pinball_components/test/src/components/golden/launch-ramp.png deleted file mode 100644 index 50a4ed4c..00000000 Binary files a/packages/pinball_components/test/src/components/golden/launch-ramp.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/launch_ramp.png b/packages/pinball_components/test/src/components/golden/launch_ramp.png new file mode 100644 index 00000000..0ff6ef45 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/launch_ramp.png differ diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x2-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x2_dimmed.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x2-dimmed.png rename to packages/pinball_components/test/src/components/golden/multipliers/x2_dimmed.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x2-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x2_lit.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x2-lit.png rename to packages/pinball_components/test/src/components/golden/multipliers/x2_lit.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x3-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x3_dimmed.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x3-dimmed.png rename to packages/pinball_components/test/src/components/golden/multipliers/x3_dimmed.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x3-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x3_lit.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x3-lit.png rename to packages/pinball_components/test/src/components/golden/multipliers/x3_lit.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x4-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x4_dimmed.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x4-dimmed.png rename to packages/pinball_components/test/src/components/golden/multipliers/x4_dimmed.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x4-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x4_lit.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x4-lit.png rename to packages/pinball_components/test/src/components/golden/multipliers/x4_lit.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x5-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x5_dimmed.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x5-dimmed.png rename to packages/pinball_components/test/src/components/golden/multipliers/x5_dimmed.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x5-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x5_lit.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x5-lit.png rename to packages/pinball_components/test/src/components/golden/multipliers/x5_lit.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x6-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x6_dimmed.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x6-dimmed.png rename to packages/pinball_components/test/src/components/golden/multipliers/x6_dimmed.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x6-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x6_lit.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x6-lit.png rename to packages/pinball_components/test/src/components/golden/multipliers/x6_lit.png diff --git a/packages/pinball_components/test/src/components/golden/plunger/pull.png b/packages/pinball_components/test/src/components/golden/plunger/pull.png index 0ec27a4e..cdbb3e31 100644 Binary files a/packages/pinball_components/test/src/components/golden/plunger/pull.png and b/packages/pinball_components/test/src/components/golden/plunger/pull.png differ diff --git a/packages/pinball_components/test/src/components/golden/plunger/release.png b/packages/pinball_components/test/src/components/golden/plunger/release.png index 61f7a4d9..cda853c3 100644 Binary files a/packages/pinball_components/test/src/components/golden/plunger/release.png and b/packages/pinball_components/test/src/components/golden/plunger/release.png differ diff --git a/packages/pinball_components/test/src/components/golden/rocket.png b/packages/pinball_components/test/src/components/golden/rocket.png index f9dc36f8..8ee4009d 100644 Binary files a/packages/pinball_components/test/src/components/golden/rocket.png and b/packages/pinball_components/test/src/components/golden/rocket.png differ diff --git a/packages/pinball_components/test/src/components/golden/score/1m.png b/packages/pinball_components/test/src/components/golden/score/1m.png index bb2f5631..7045a039 100644 Binary files a/packages/pinball_components/test/src/components/golden/score/1m.png and b/packages/pinball_components/test/src/components/golden/score/1m.png differ diff --git a/packages/pinball_components/test/src/components/golden/score/200k.png b/packages/pinball_components/test/src/components/golden/score/200k.png index c25d116b..46503f3d 100644 Binary files a/packages/pinball_components/test/src/components/golden/score/200k.png and b/packages/pinball_components/test/src/components/golden/score/200k.png differ diff --git a/packages/pinball_components/test/src/components/golden/score/20k.png b/packages/pinball_components/test/src/components/golden/score/20k.png index 2a4446c3..42721119 100644 Binary files a/packages/pinball_components/test/src/components/golden/score/20k.png and b/packages/pinball_components/test/src/components/golden/score/20k.png differ diff --git a/packages/pinball_components/test/src/components/golden/score/5k.png b/packages/pinball_components/test/src/components/golden/score/5k.png index 8f2a7973..1e5117f1 100644 Binary files a/packages/pinball_components/test/src/components/golden/score/5k.png and b/packages/pinball_components/test/src/components/golden/score/5k.png differ diff --git a/packages/pinball_components/test/src/components/golden/signpost/active1.png b/packages/pinball_components/test/src/components/golden/signpost/active1.png index 0e0f9e79..9b8ac5dd 100644 Binary files a/packages/pinball_components/test/src/components/golden/signpost/active1.png and b/packages/pinball_components/test/src/components/golden/signpost/active1.png differ diff --git a/packages/pinball_components/test/src/components/golden/signpost/active2.png b/packages/pinball_components/test/src/components/golden/signpost/active2.png index 9dfae564..e86ee7bf 100644 Binary files a/packages/pinball_components/test/src/components/golden/signpost/active2.png and b/packages/pinball_components/test/src/components/golden/signpost/active2.png differ diff --git a/packages/pinball_components/test/src/components/golden/signpost/active3.png b/packages/pinball_components/test/src/components/golden/signpost/active3.png index a99c9e48..557e9605 100644 Binary files a/packages/pinball_components/test/src/components/golden/signpost/active3.png and b/packages/pinball_components/test/src/components/golden/signpost/active3.png differ diff --git a/packages/pinball_components/test/src/components/golden/signpost/inactive.png b/packages/pinball_components/test/src/components/golden/signpost/inactive.png index 7f089716..5e67d706 100644 Binary files a/packages/pinball_components/test/src/components/golden/signpost/inactive.png and b/packages/pinball_components/test/src/components/golden/signpost/inactive.png differ diff --git a/packages/pinball_components/test/src/components/golden/spaceship-rail.png b/packages/pinball_components/test/src/components/golden/spaceship_rail.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/spaceship-rail.png rename to packages/pinball_components/test/src/components/golden/spaceship_rail.png diff --git a/packages/pinball_components/test/src/components/golden/sparky-computer.png b/packages/pinball_components/test/src/components/golden/sparky-computer.png deleted file mode 100644 index ebe2e98e..00000000 Binary files a/packages/pinball_components/test/src/components/golden/sparky-computer.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/sparky_animatronic/end.png b/packages/pinball_components/test/src/components/golden/sparky_animatronic/end.png index 5e963f14..d499e754 100644 Binary files a/packages/pinball_components/test/src/components/golden/sparky_animatronic/end.png and b/packages/pinball_components/test/src/components/golden/sparky_animatronic/end.png differ diff --git a/packages/pinball_components/test/src/components/golden/sparky_animatronic/middle.png b/packages/pinball_components/test/src/components/golden/sparky_animatronic/middle.png index 2665c5cb..dea793d9 100644 Binary files a/packages/pinball_components/test/src/components/golden/sparky_animatronic/middle.png and b/packages/pinball_components/test/src/components/golden/sparky_animatronic/middle.png differ diff --git a/packages/pinball_components/test/src/components/golden/sparky_animatronic/start.png b/packages/pinball_components/test/src/components/golden/sparky_animatronic/start.png index ea3e6344..663d8090 100644 Binary files a/packages/pinball_components/test/src/components/golden/sparky_animatronic/start.png and b/packages/pinball_components/test/src/components/golden/sparky_animatronic/start.png differ diff --git a/packages/pinball_components/test/src/components/golden/sparky_computer.png b/packages/pinball_components/test/src/components/golden/sparky_computer.png new file mode 100644 index 00000000..22fdc7e5 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/sparky_computer.png differ diff --git a/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart b/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart index afd4c130..1ef5e7a7 100644 --- a/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart +++ b/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart @@ -102,9 +102,6 @@ void main() { expect(() => GoogleLetter(6), throwsA(isA())); }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockGoogleLetterCubit(); whenListen( diff --git a/packages/pinball_components/test/src/components/initial_position_test.dart b/packages/pinball_components/test/src/components/initial_position_test.dart index 9f015150..b7495181 100644 --- a/packages/pinball_components/test/src/components/initial_position_test.dart +++ b/packages/pinball_components/test/src/components/initial_position_test.dart @@ -54,7 +54,7 @@ void main() { ); flameTester.test( - 'deafaults to zero ' + 'defaults to zero ' 'when no initialPosition is given', (game) async { final component = TestBodyComponent(); diff --git a/packages/pinball_components/test/src/components/kicker_test.dart b/packages/pinball_components/test/src/components/kicker_test.dart index 4d3fc14d..e347032a 100644 --- a/packages/pinball_components/test/src/components/kicker_test.dart +++ b/packages/pinball_components/test/src/components/kicker_test.dart @@ -61,9 +61,6 @@ void main() { }, ); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockKickerCubit(); whenListen( diff --git a/packages/pinball_components/test/src/components/launch_ramp_test.dart b/packages/pinball_components/test/src/components/launch_ramp_test.dart index 38c0920b..11033b5a 100644 --- a/packages/pinball_components/test/src/components/launch_ramp_test.dart +++ b/packages/pinball_components/test/src/components/launch_ramp_test.dart @@ -36,7 +36,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('golden/launch-ramp.png'), + matchesGoldenFile('golden/launch_ramp.png'), ); }, ); diff --git a/packages/pinball_components/test/src/components/layer_sensor/behavior/layer_filtering_behavior_test.dart b/packages/pinball_components/test/src/components/layer_sensor/behavior/layer_filtering_behavior_test.dart index b7bc308b..b8701656 100644 --- a/packages/pinball_components/test/src/components/layer_sensor/behavior/layer_filtering_behavior_test.dart +++ b/packages/pinball_components/test/src/components/layer_sensor/behavior/layer_filtering_behavior_test.dart @@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/src/components/layer_sensor/behaviors/behaviors.dart'; +import 'package:pinball_flame/pinball_flame.dart'; import '../../../../helpers/helpers.dart'; diff --git a/packages/pinball_components/test/src/components/layer_sensor/layer_sensor_test.dart b/packages/pinball_components/test/src/components/layer_sensor/layer_sensor_test.dart index dd32ad56..2966b106 100644 --- a/packages/pinball_components/test/src/components/layer_sensor/layer_sensor_test.dart +++ b/packages/pinball_components/test/src/components/layer_sensor/layer_sensor_test.dart @@ -4,6 +4,7 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/src/components/layer_sensor/behaviors/behaviors.dart'; +import 'package:pinball_flame/pinball_flame.dart'; import '../../../helpers/helpers.dart'; diff --git a/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart b/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart index c612ecb9..0a28ae54 100644 --- a/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart +++ b/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart @@ -122,7 +122,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x2-lit.png'), + matchesGoldenFile('../golden/multipliers/x2_lit.png'), ); }, ); @@ -162,7 +162,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x2-dimmed.png'), + matchesGoldenFile('../golden/multipliers/x2_dimmed.png'), ); }, ); @@ -206,7 +206,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x3-lit.png'), + matchesGoldenFile('../golden/multipliers/x3_lit.png'), ); }, ); @@ -246,7 +246,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x3-dimmed.png'), + matchesGoldenFile('../golden/multipliers/x3_dimmed.png'), ); }, ); @@ -290,7 +290,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x4-lit.png'), + matchesGoldenFile('../golden/multipliers/x4_lit.png'), ); }, ); @@ -330,7 +330,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x4-dimmed.png'), + matchesGoldenFile('../golden/multipliers/x4_dimmed.png'), ); }, ); @@ -374,7 +374,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x5-lit.png'), + matchesGoldenFile('../golden/multipliers/x5_lit.png'), ); }, ); @@ -414,7 +414,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x5-dimmed.png'), + matchesGoldenFile('../golden/multipliers/x5_dimmed.png'), ); }, ); @@ -458,7 +458,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x6-lit.png'), + matchesGoldenFile('../golden/multipliers/x6_lit.png'), ); }, ); @@ -498,7 +498,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x6-dimmed.png'), + matchesGoldenFile('../golden/multipliers/x6_dimmed.png'), ); }, ); diff --git a/packages/pinball_components/test/src/components/plunger_test.dart b/packages/pinball_components/test/src/components/plunger_test.dart index ea1ba826..e28bdaed 100644 --- a/packages/pinball_components/test/src/components/plunger_test.dart +++ b/packages/pinball_components/test/src/components/plunger_test.dart @@ -329,7 +329,7 @@ void main() { ); flameTester.test( - 'connected body collison enabled', + 'connected body collision enabled', (game) async { await game.ensureAdd(plunger); await game.ensureAdd(anchor); diff --git a/packages/pinball_components/test/src/components/score_component/behaviors/score_component_scaling_behavior_test.dart b/packages/pinball_components/test/src/components/score_component/behaviors/score_component_scaling_behavior_test.dart new file mode 100644 index 00000000..9bf0db2f --- /dev/null +++ b/packages/pinball_components/test/src/components/score_component/behaviors/score_component_scaling_behavior_test.dart @@ -0,0 +1,74 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame/effects.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/score_component/behaviors/behaviors.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + group('ScoreComponentScalingBehavior', () { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester( + () => TestGame([ + Assets.images.score.fiveThousand.keyName, + Assets.images.score.twentyThousand.keyName, + Assets.images.score.twoHundredThousand.keyName, + Assets.images.score.oneMillion.keyName, + ]), + ); + + test('can be instantiated', () { + expect( + ScoreComponentScalingBehavior(), + isA(), + ); + }); + + flameTester.test('can be loaded', (game) async { + final parent = ScoreComponent.test( + points: Points.fiveThousand, + position: Vector2.zero(), + effectController: EffectController(duration: 1), + ); + final behavior = ScoreComponentScalingBehavior(); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + + expect(parent.children, contains(behavior)); + }); + + flameTester.test( + 'scales the sprite', + (game) async { + final parent1 = ScoreComponent.test( + points: Points.fiveThousand, + position: Vector2(0, 10), + effectController: EffectController(duration: 1), + ); + final parent2 = ScoreComponent.test( + points: Points.fiveThousand, + position: Vector2(0, -10), + effectController: EffectController(duration: 1), + ); + await game.ensureAddAll([parent1, parent2]); + + await parent1.ensureAdd(ScoreComponentScalingBehavior()); + await parent2.ensureAdd(ScoreComponentScalingBehavior()); + game.update(1); + + expect( + parent1.scale.x, + greaterThan(parent2.scale.x), + ); + expect( + parent1.scale.y, + greaterThan(parent2.scale.y), + ); + }, + ); + }); +} diff --git a/packages/pinball_components/test/src/components/score_component_test.dart b/packages/pinball_components/test/src/components/score_component/score_component_test.dart similarity index 72% rename from packages/pinball_components/test/src/components/score_component_test.dart rename to packages/pinball_components/test/src/components/score_component/score_component_test.dart index f2bd52e3..00c08d5c 100644 --- a/packages/pinball_components/test/src/components/score_component_test.dart +++ b/packages/pinball_components/test/src/components/score_component/score_component_test.dart @@ -5,24 +5,37 @@ import 'package:flame/effects.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/score_component/behaviors/behaviors.dart'; -import '../../helpers/helpers.dart'; +import '../../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.score.fiveThousand.keyName, - Assets.images.score.twentyThousand.keyName, - Assets.images.score.twoHundredThousand.keyName, - Assets.images.score.oneMillion.keyName, - ]; - final flameTester = FlameTester(() => TestGame(assets)); + final flameTester = FlameTester( + () => TestGame([ + Assets.images.score.fiveThousand.keyName, + Assets.images.score.twentyThousand.keyName, + Assets.images.score.twoHundredThousand.keyName, + Assets.images.score.oneMillion.keyName, + ]), + ); group('ScoreComponent', () { + test('can be instantiated', () { + expect( + ScoreComponent( + points: Points.fiveThousand, + position: Vector2.zero(), + effectController: EffectController(duration: 1), + ), + isA(), + ); + }); + flameTester.testGameWidget( 'loads correctly', setUp: (game, tester) async { - await game.images.loadAll(assets); + await game.onLoad(); game.camera.followVector2(Vector2.zero()); await game.ensureAdd( ScoreComponent( @@ -38,13 +51,32 @@ void main() { }, ); + flameTester.test( + 'adds a ScoreComponentScalingBehavior', + (game) async { + await game.onLoad(); + game.camera.followVector2(Vector2.zero()); + final component = ScoreComponent( + points: Points.oneMillion, + position: Vector2.zero(), + effectController: EffectController(duration: 1), + ); + await game.ensureAdd(component); + + expect( + component.children.whereType().length, + equals(1), + ); + }, + ); + flameTester.testGameWidget( 'has a movement effect', setUp: (game, tester) async { - await game.images.loadAll(assets); + await game.onLoad(); game.camera.followVector2(Vector2.zero()); await game.ensureAdd( - ScoreComponent( + ScoreComponent.test( points: Points.oneMillion, position: Vector2.zero(), effectController: EffectController(duration: 1), @@ -63,10 +95,10 @@ void main() { flameTester.testGameWidget( 'is removed once finished', setUp: (game, tester) async { - await game.images.loadAll(assets); + await game.onLoad(); game.camera.followVector2(Vector2.zero()); await game.ensureAdd( - ScoreComponent( + ScoreComponent.test( points: Points.oneMillion, position: Vector2.zero(), effectController: EffectController(duration: 1), @@ -83,12 +115,14 @@ void main() { ); group('renders correctly', () { + const goldensPath = '../golden/score/'; + flameTester.testGameWidget( '5000 points', setUp: (game, tester) async { - await game.images.loadAll(assets); + await game.onLoad(); await game.ensureAdd( - ScoreComponent( + ScoreComponent.test( points: Points.fiveThousand, position: Vector2.zero(), effectController: EffectController(duration: 1), @@ -104,7 +138,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('golden/score/5k.png'), + matchesGoldenFile('${goldensPath}5k.png'), ); }, ); @@ -112,9 +146,9 @@ void main() { flameTester.testGameWidget( '20000 points', setUp: (game, tester) async { - await game.images.loadAll(assets); + await game.onLoad(); await game.ensureAdd( - ScoreComponent( + ScoreComponent.test( points: Points.twentyThousand, position: Vector2.zero(), effectController: EffectController(duration: 1), @@ -130,7 +164,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('golden/score/20k.png'), + matchesGoldenFile('${goldensPath}20k.png'), ); }, ); @@ -138,9 +172,9 @@ void main() { flameTester.testGameWidget( '200000 points', setUp: (game, tester) async { - await game.images.loadAll(assets); + await game.onLoad(); await game.ensureAdd( - ScoreComponent( + ScoreComponent.test( points: Points.twoHundredThousand, position: Vector2.zero(), effectController: EffectController(duration: 1), @@ -156,7 +190,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('golden/score/200k.png'), + matchesGoldenFile('${goldensPath}200k.png'), ); }, ); @@ -164,9 +198,9 @@ void main() { flameTester.testGameWidget( '1000000 points', setUp: (game, tester) async { - await game.images.loadAll(assets); + await game.onLoad(); await game.ensureAdd( - ScoreComponent( + ScoreComponent.test( points: Points.oneMillion, position: Vector2.zero(), effectController: EffectController(duration: 1), @@ -182,7 +216,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('golden/score/1m.png'), + matchesGoldenFile('${goldensPath}1m.png'), ); }, ); diff --git a/packages/pinball_components/test/src/components/shapes/arc_shape_test.dart b/packages/pinball_components/test/src/components/shapes/arc_shape_test.dart deleted file mode 100644 index fe778872..00000000 --- a/packages/pinball_components/test/src/components/shapes/arc_shape_test.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'dart:math' as math; -import 'package:flame/extensions.dart'; -import 'package:flame/game.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball_components/src/components/components.dart'; - -void main() { - group('ArcShape', () { - test('can be instantiated', () { - expect( - ArcShape( - center: Vector2.zero(), - arcRadius: 10, - angle: 2 * math.pi, - ), - isNotNull, - ); - }); - - group('copyWith', () { - test( - 'copies correctly ' - 'when no argument specified', () { - final arcShape = ArcShape( - center: Vector2.zero(), - arcRadius: 10, - angle: 2 * math.pi, - ); - final arcShapeCopied = arcShape.copyWith(); - - for (var index = 0; index < arcShape.vertices.length; index++) { - expect( - arcShape.vertices[index], - equals(arcShapeCopied.vertices[index]), - ); - } - }); - - test( - 'copies correctly ' - 'when all arguments specified', () { - final arcShapeExpected = ArcShape( - center: Vector2.all(10), - arcRadius: 15, - angle: 2 * math.pi, - ); - final arcShapeCopied = ArcShape( - center: Vector2.zero(), - arcRadius: 10, - angle: math.pi, - ).copyWith( - center: Vector2.all(10), - arcRadius: 15, - angle: 2 * math.pi, - ); - - for (var index = 0; index < arcShapeCopied.vertices.length; index++) { - expect( - arcShapeCopied.vertices[index], - equals(arcShapeExpected.vertices[index]), - ); - } - }); - }); - }); -} diff --git a/packages/pinball_components/test/src/components/shapes/bezier_curve_shape_test.dart b/packages/pinball_components/test/src/components/shapes/bezier_curve_shape_test.dart deleted file mode 100644 index 6a6adeb7..00000000 --- a/packages/pinball_components/test/src/components/shapes/bezier_curve_shape_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'dart:math' as math; -import 'package:flame/extensions.dart'; -import 'package:flame/game.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball_components/src/components/components.dart'; - -void main() { - group('BezierCurveShape', () { - final controlPoints = [ - Vector2(0, 0), - Vector2(10, 0), - Vector2(0, 10), - Vector2(10, 10), - ]; - - test('can be instantiated', () { - expect( - BezierCurveShape( - controlPoints: controlPoints, - ), - isNotNull, - ); - }); - - group('rotate', () { - test('returns vertices rotated', () { - const rotationAngle = 2 * math.pi; - final controlPoints = [ - Vector2(0, 0), - Vector2(10, 0), - Vector2(0, 10), - Vector2(10, 10), - ]; - - final bezierCurveShape = BezierCurveShape( - controlPoints: controlPoints, - ); - final bezierCurveShapeRotated = BezierCurveShape( - controlPoints: controlPoints, - )..rotate(rotationAngle); - - for (var index = 0; index < bezierCurveShape.vertices.length; index++) { - expect( - bezierCurveShape.vertices[index]..rotate(rotationAngle), - equals(bezierCurveShapeRotated.vertices[index]), - ); - } - }); - }); - }); -} diff --git a/packages/pinball_components/test/src/components/shapes/ellipse_shape_test.dart b/packages/pinball_components/test/src/components/shapes/ellipse_shape_test.dart deleted file mode 100644 index 31f45cc1..00000000 --- a/packages/pinball_components/test/src/components/shapes/ellipse_shape_test.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'dart:math' as math; -import 'package:flame/extensions.dart'; -import 'package:flame/game.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball_components/src/components/components.dart'; - -void main() { - group('EllipseShape', () { - test('can be instantiated', () { - expect( - EllipseShape( - center: Vector2.zero(), - majorRadius: 10, - minorRadius: 8, - ), - isNotNull, - ); - }); - - group('rotate', () { - test('returns vertices rotated', () { - const rotationAngle = 2 * math.pi; - final ellipseShape = EllipseShape( - center: Vector2.zero(), - majorRadius: 10, - minorRadius: 8, - ); - final ellipseShapeRotated = EllipseShape( - center: Vector2.zero(), - majorRadius: 10, - minorRadius: 8, - )..rotate(rotationAngle); - - for (var index = 0; index < ellipseShape.vertices.length; index++) { - expect( - ellipseShape.vertices[index]..rotate(rotationAngle), - equals(ellipseShapeRotated.vertices[index]), - ); - } - }); - }); - - group('copyWith', () { - test('returns same shape when no properties are passed', () { - final ellipseShape = EllipseShape( - center: Vector2.zero(), - majorRadius: 10, - minorRadius: 8, - ); - final ellipseShapeCopied = ellipseShape.copyWith(); - - for (var index = 0; index < ellipseShape.vertices.length; index++) { - expect( - ellipseShape.vertices[index], - equals(ellipseShapeCopied.vertices[index]), - ); - } - }); - - test('returns object with updated properties when are passed', () { - final ellipseShapeExpected = EllipseShape( - center: Vector2.all(10), - majorRadius: 10, - minorRadius: 8, - ); - final ellipseShapeCopied = EllipseShape( - center: Vector2.zero(), - majorRadius: 10, - minorRadius: 8, - ).copyWith(center: Vector2.all(10)); - - for (var index = 0; - index < ellipseShapeCopied.vertices.length; - index++) { - expect( - ellipseShapeCopied.vertices[index], - equals(ellipseShapeExpected.vertices[index]), - ); - } - }); - }); - }); -} diff --git a/packages/pinball_components/test/src/components/skill_shot/skill_shot_test.dart b/packages/pinball_components/test/src/components/skill_shot/skill_shot_test.dart index dabacc69..beb159b8 100644 --- a/packages/pinball_components/test/src/components/skill_shot/skill_shot_test.dart +++ b/packages/pinball_components/test/src/components/skill_shot/skill_shot_test.dart @@ -29,9 +29,6 @@ void main() { expect(game.contains(skillShot), isTrue); }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockSkillShotCubit(); whenListen( diff --git a/packages/pinball_components/test/src/components/spaceship_rail_test.dart b/packages/pinball_components/test/src/components/spaceship_rail_test.dart index 65e9dbd7..78aa4aaa 100644 --- a/packages/pinball_components/test/src/components/spaceship_rail_test.dart +++ b/packages/pinball_components/test/src/components/spaceship_rail_test.dart @@ -35,7 +35,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('golden/spaceship-rail.png'), + matchesGoldenFile('golden/spaceship_rail.png'), ); }, ); diff --git a/packages/pinball_components/test/src/components/spaceship_ramp/behavior/ramp_ball_ascending_contact_behavior_test.dart b/packages/pinball_components/test/src/components/spaceship_ramp/behavior/ramp_ball_ascending_contact_behavior_test.dart index ea37550a..d1f03ce7 100644 --- a/packages/pinball_components/test/src/components/spaceship_ramp/behavior/ramp_ball_ascending_contact_behavior_test.dart +++ b/packages/pinball_components/test/src/components/spaceship_ramp/behavior/ramp_ball_ascending_contact_behavior_test.dart @@ -67,16 +67,16 @@ void main() { initialState: const SpaceshipRampState.initial(), ); - final rampSensor = RampScoringSensor.test(); + final parent = SpaceshipRampBoardOpening.test(); final spaceshipRamp = SpaceshipRamp.test( bloc: bloc, ); when(() => body.linearVelocity).thenReturn(Vector2(0, -1)); - await spaceshipRamp.add(rampSensor); + await spaceshipRamp.add(parent); await game.ensureAddAll([spaceshipRamp, ball]); - await rampSensor.add(behavior); + await parent.add(behavior); behavior.beginContact(ball, _MockContact()); @@ -95,16 +95,16 @@ void main() { initialState: const SpaceshipRampState.initial(), ); - final rampSensor = RampScoringSensor.test(); + final parent = SpaceshipRampBoardOpening.test(); final spaceshipRamp = SpaceshipRamp.test( bloc: bloc, ); when(() => body.linearVelocity).thenReturn(Vector2(0, 1)); - await spaceshipRamp.add(rampSensor); + await spaceshipRamp.add(parent); await game.ensureAddAll([spaceshipRamp, ball]); - await rampSensor.add(behavior); + await parent.add(behavior); behavior.beginContact(ball, _MockContact()); diff --git a/packages/pinball_components/test/src/components/spaceship_ramp/spaceship_ramp_test.dart b/packages/pinball_components/test/src/components/spaceship_ramp/spaceship_ramp_test.dart index b74cfb88..1c9c968d 100644 --- a/packages/pinball_components/test/src/components/spaceship_ramp/spaceship_ramp_test.dart +++ b/packages/pinball_components/test/src/components/spaceship_ramp/spaceship_ramp_test.dart @@ -2,16 +2,24 @@ import 'package:bloc_test/bloc_test.dart'; import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/spaceship_ramp/behavior/behavior.dart'; import 'package:pinball_flame/pinball_flame.dart'; import '../../../helpers/helpers.dart'; class _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {} +class _MockBall extends Mock implements Ball {} + +class _MockContact extends Mock implements Contact {} + +class _MockManifold extends Mock implements Manifold {} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); final assets = [ @@ -275,4 +283,71 @@ void main() { }); }); }); + + group('SpaceshipRampBase', () { + test('can be instantiated', () { + expect(SpaceshipRampBase(), isA()); + }); + + flameTester.test('can be loaded', (game) async { + final component = SpaceshipRampBase(); + await game.ensureAdd(component); + expect(game.children, contains(component)); + }); + + flameTester.test( + 'postSolves disables contact when ball is not on Layer.board', + (game) async { + final ball = _MockBall(); + final contact = _MockContact(); + when(() => ball.layer).thenReturn(Layer.spaceshipEntranceRamp); + final component = SpaceshipRampBase(); + await game.ensureAdd(component); + + component.preSolve(ball, contact, _MockManifold()); + + verify(() => contact.setEnabled(false)).called(1); + }, + ); + + flameTester.test( + 'postSolves enables contact when ball is on Layer.board', + (game) async { + final ball = _MockBall(); + final contact = _MockContact(); + when(() => ball.layer).thenReturn(Layer.board); + final component = SpaceshipRampBase(); + await game.ensureAdd(component); + + component.preSolve(ball, contact, _MockManifold()); + + verify(() => contact.setEnabled(true)).called(1); + }, + ); + }); + + group('SpaceshipRampBoardOpening', () { + test('can be instantiated', () { + expect(SpaceshipRampBoardOpening(), isA()); + }); + + flameTester.test('can be loaded', (game) async { + final parent = SpaceshipRamp.test(bloc: _MockSpaceshipRampCubit()); + final component = SpaceshipRampBoardOpening(); + await game.ensureAdd(parent); + await parent.ensureAdd(component); + expect(parent.children, contains(component)); + }); + + flameTester.test('adds a RampBallAscendingContactBehavior', (game) async { + final parent = SpaceshipRamp.test(bloc: _MockSpaceshipRampCubit()); + final component = SpaceshipRampBoardOpening(); + await game.ensureAdd(parent); + await parent.ensureAdd(component); + expect( + component.children.whereType().length, + equals(1), + ); + }); + }); } diff --git a/packages/pinball_components/test/src/components/sparky_bumper/sparky_bumper_test.dart b/packages/pinball_components/test/src/components/sparky_bumper/sparky_bumper_test.dart index 7544fdd2..45c55790 100644 --- a/packages/pinball_components/test/src/components/sparky_bumper/sparky_bumper_test.dart +++ b/packages/pinball_components/test/src/components/sparky_bumper/sparky_bumper_test.dart @@ -44,9 +44,6 @@ void main() { expect(game.contains(sparkyBumper), isTrue); }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockSparkyBumperCubit(); whenListen( diff --git a/packages/pinball_components/test/src/components/sparky_computer/sparky_computer_test.dart b/packages/pinball_components/test/src/components/sparky_computer/sparky_computer_test.dart index ffb14fd8..0c045cb1 100644 --- a/packages/pinball_components/test/src/components/sparky_computer/sparky_computer_test.dart +++ b/packages/pinball_components/test/src/components/sparky_computer/sparky_computer_test.dart @@ -42,14 +42,11 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('../golden/sparky-computer.png'), + matchesGoldenFile('../golden/sparky_computer.png'), ); }, ); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 - // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockSparkyComputerCubit(); whenListen( diff --git a/packages/pinball_flame/lib/pinball_flame.dart b/packages/pinball_flame/lib/pinball_flame.dart index 38f09b59..c40405cb 100644 --- a/packages/pinball_flame/lib/pinball_flame.dart +++ b/packages/pinball_flame/lib/pinball_flame.dart @@ -1,10 +1,12 @@ library pinball_flame; +export 'src/behaviors/behaviors.dart'; export 'src/canvas/canvas.dart'; export 'src/component_controller.dart'; -export 'src/contact_behavior.dart'; export 'src/flame_provider.dart'; export 'src/keyboard_input_controller.dart'; +export 'src/layer.dart'; export 'src/parent_is_a.dart'; export 'src/pinball_forge2d_game.dart'; +export 'src/shapes/shapes.dart'; export 'src/sprite_animation.dart'; diff --git a/packages/pinball_flame/lib/src/behaviors/behaviors.dart b/packages/pinball_flame/lib/src/behaviors/behaviors.dart new file mode 100644 index 00000000..fa80c97f --- /dev/null +++ b/packages/pinball_flame/lib/src/behaviors/behaviors.dart @@ -0,0 +1,3 @@ +export 'contact_behavior.dart'; +export 'layer_contact_behavior.dart'; +export 'z_index_contact_behavior.dart'; diff --git a/packages/pinball_flame/lib/src/contact_behavior.dart b/packages/pinball_flame/lib/src/behaviors/contact_behavior.dart similarity index 95% rename from packages/pinball_flame/lib/src/contact_behavior.dart rename to packages/pinball_flame/lib/src/behaviors/contact_behavior.dart index 92f108d8..855bb620 100644 --- a/packages/pinball_flame/lib/src/contact_behavior.dart +++ b/packages/pinball_flame/lib/src/behaviors/contact_behavior.dart @@ -10,8 +10,6 @@ import 'package:pinball_flame/pinball_flame.dart'; /// /// It does so by grouping the userData in a [_UserData], and resetting the /// parent's userData accordingly. -// TODO(alestiago): Make use of generics to infer the type of the contact. -// https://github.com/VGVentures/pinball/pull/234#discussion_r859182267 class ContactBehavior extends Component with ContactCallbacks, ParentIsA { final _fixturesUserData = {}; diff --git a/packages/pinball_flame/lib/src/behaviors/layer_contact_behavior.dart b/packages/pinball_flame/lib/src/behaviors/layer_contact_behavior.dart new file mode 100644 index 00000000..ef475bc9 --- /dev/null +++ b/packages/pinball_flame/lib/src/behaviors/layer_contact_behavior.dart @@ -0,0 +1,25 @@ +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// {@template layer_contact_behavior} +/// Switches the [Layer] of any [Layered] body that contacts with it. +/// {@endtemplate} +class LayerContactBehavior extends ContactBehavior { + /// {@macro layer_contact_behavior} + LayerContactBehavior({ + required Layer layer, + bool onBegin = true, + }) { + if (onBegin) { + onBeginContact = (other, _) => _changeLayer(other, layer); + } else { + onEndContact = (other, _) => _changeLayer(other, layer); + } + } + + void _changeLayer(Object other, Layer layer) { + if (other is! Layered) return; + if (other.layer == layer) return; + other.layer = layer; + } +} diff --git a/packages/pinball_flame/lib/src/behaviors/z_index_contact_behavior.dart b/packages/pinball_flame/lib/src/behaviors/z_index_contact_behavior.dart new file mode 100644 index 00000000..c763e2cf --- /dev/null +++ b/packages/pinball_flame/lib/src/behaviors/z_index_contact_behavior.dart @@ -0,0 +1,25 @@ +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// {@template layer_contact_behavior} +/// Switches the z-index of any [ZIndex] body that contacts with it. +/// {@endtemplate} +class ZIndexContactBehavior extends ContactBehavior { + /// {@macro layer_contact_behavior} + ZIndexContactBehavior({ + required int zIndex, + bool onBegin = true, + }) { + if (onBegin) { + onBeginContact = (other, _) => _changeZIndex(other, zIndex); + } else { + onEndContact = (other, _) => _changeZIndex(other, zIndex); + } + } + + void _changeZIndex(Object other, int zIndex) { + if (other is! ZIndex) return; + if (other.zIndex == zIndex) return; + other.zIndex = zIndex; + } +} diff --git a/packages/pinball_flame/lib/src/canvas/canvas_component.dart b/packages/pinball_flame/lib/src/canvas/canvas_component.dart index ca6e64d0..394b0852 100644 --- a/packages/pinball_flame/lib/src/canvas/canvas_component.dart +++ b/packages/pinball_flame/lib/src/canvas/canvas_component.dart @@ -12,7 +12,7 @@ typedef PaintFunction = void Function(Paint); /// {@template canvas_component} /// Allows listening before the rendering of [Sprite]s. /// -/// The existance of this class is to hack around the fact that Flame doesn't +/// The existence of this class is to hack around the fact that Flame doesn't /// provide a global way to modify the default [Paint] before rendering a /// [Sprite]. /// {@endtemplate} diff --git a/packages/pinball_flame/lib/src/canvas/canvas_wrapper.dart b/packages/pinball_flame/lib/src/canvas/canvas_wrapper.dart index 883527d2..3abe8c1c 100644 --- a/packages/pinball_flame/lib/src/canvas/canvas_wrapper.dart +++ b/packages/pinball_flame/lib/src/canvas/canvas_wrapper.dart @@ -1,9 +1,11 @@ -// ignore_for_file: public_member_api_docs - import 'dart:typed_data'; import 'dart:ui'; +/// {@template canvas_wrapper} +/// Custom [Canvas] implementation for Pinball +/// {@endtemplate} class CanvasWrapper implements Canvas { + /// [Canvas] used for painting operations late Canvas canvas; @override diff --git a/packages/pinball_flame/lib/src/flame_provider.dart b/packages/pinball_flame/lib/src/flame_provider.dart index 85a1004c..dcc6a5ae 100644 --- a/packages/pinball_flame/lib/src/flame_provider.dart +++ b/packages/pinball_flame/lib/src/flame_provider.dart @@ -1,21 +1,28 @@ -// ignore_for_file: public_member_api_docs - import 'package:bloc/bloc.dart'; import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; +/// {@template flame_provider} +/// Provider-style component, similar to Provider in Flutter, but used to +/// retrieve [Component] objects previously provided +/// {@endtemplate} class FlameProvider extends Component { + //// {@macro flame_provider} FlameProvider.value( this.provider, { Iterable? children, - }) : super( - children: children, - ); + }) : super(children: children); + /// The object that needs to be provided final T provider; } +//// {@template multi_flame_provider} +/// MultiProvider-style component, similar to MultiProvider in Flutter, +/// but used to retrieve more than one [Component] object previously provided +/// {@endtemplate} class MultiFlameProvider extends Component { + /// {@macro multi_flame_provider} MultiFlameProvider({ required List> providers, Iterable? children, @@ -54,7 +61,9 @@ class MultiFlameProvider extends Component { } } +/// Extended API on [Component] extension ReadFlameProvider on Component { + /// Retrieve an object of type [T] that was previously provided T readProvider() { final providers = ancestors().whereType>(); assert( @@ -64,9 +73,8 @@ extension ReadFlameProvider on Component { return providers.first.provider; } -} -extension ReadFlameBlocProvider on Component { + /// Retrieve a bloc [B] with state [S] previously provided B readBloc, S>() { final providers = ancestors().whereType>(); assert( diff --git a/packages/pinball_flame/lib/src/keyboard_input_controller.dart b/packages/pinball_flame/lib/src/keyboard_input_controller.dart index 8249e599..b0d64ee6 100644 --- a/packages/pinball_flame/lib/src/keyboard_input_controller.dart +++ b/packages/pinball_flame/lib/src/keyboard_input_controller.dart @@ -1,4 +1,5 @@ import 'package:flame/components.dart'; +import 'package:flame/game.dart'; import 'package:flutter/services.dart'; /// The signature for a key handle function @@ -18,6 +19,17 @@ class KeyboardInputController extends Component with KeyboardHandler { final Map _keyUp; final Map _keyDown; + /// Trigger a virtual key up event. + bool onVirtualKeyUp(LogicalKeyboardKey key) { + final handler = _keyUp[key]; + + if (handler != null) { + return handler(); + } + + return true; + } + @override bool onKeyEvent(RawKeyEvent event, Set keysPressed) { final isUp = event is RawKeyUpEvent; @@ -32,3 +44,18 @@ class KeyboardInputController extends Component with KeyboardHandler { return true; } } + +/// Add the ability to virtually trigger key events to a [FlameGame]'s +/// [KeyboardInputController]. +extension VirtualKeyEvents on FlameGame { + /// Trigger a key up + void triggerVirtualKeyUp(LogicalKeyboardKey key) { + final keyControllers = descendants().whereType(); + + for (final controller in keyControllers) { + if (!controller.onVirtualKeyUp(key)) { + break; + } + } + } +} diff --git a/packages/pinball_components/lib/src/components/layer.dart b/packages/pinball_flame/lib/src/layer.dart similarity index 97% rename from packages/pinball_components/lib/src/components/layer.dart rename to packages/pinball_flame/lib/src/layer.dart index 8418fac1..8b93a4b2 100644 --- a/packages/pinball_components/lib/src/components/layer.dart +++ b/packages/pinball_flame/lib/src/layer.dart @@ -74,7 +74,6 @@ extension LayerMaskBits on Layer { /// {@macro layer_mask_bits} @visibleForTesting int get maskBits { - // TODO(ruialonso): test bit groups once final design is implemented. switch (this) { case Layer.all: return 0xFFFF; diff --git a/packages/pinball_flame/lib/src/parent_is_a.dart b/packages/pinball_flame/lib/src/parent_is_a.dart index 19159c89..720a07b6 100644 --- a/packages/pinball_flame/lib/src/parent_is_a.dart +++ b/packages/pinball_flame/lib/src/parent_is_a.dart @@ -1,8 +1,5 @@ import 'package:flame/components.dart'; -// TODO(alestiago): Remove once the following is merged: -// https://github.com/flame-engine/flame/pull/1566 - /// A mixin that ensures a parent is of the given type [T]. mixin ParentIsA on Component { @override diff --git a/packages/pinball_flame/lib/src/pinball_forge2d_game.dart b/packages/pinball_flame/lib/src/pinball_forge2d_game.dart index 0013dd26..b98e78ae 100644 --- a/packages/pinball_flame/lib/src/pinball_forge2d_game.dart +++ b/packages/pinball_flame/lib/src/pinball_forge2d_game.dart @@ -4,7 +4,7 @@ import 'package:flame/game.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/world_contact_listener.dart'; -// NOTE(wolfen): This should be removed when https://github.com/flame-engine/flame/pull/1597 is solved. +// NOTE: This should be removed when https://github.com/flame-engine/flame/pull/1597 is solved. /// {@template pinball_forge2d_game} /// A [Game] that uses the Forge2D physics engine. /// {@endtemplate} diff --git a/packages/pinball_components/lib/src/components/shapes/arc_shape.dart b/packages/pinball_flame/lib/src/shapes/arc_shape.dart similarity index 66% rename from packages/pinball_components/lib/src/components/shapes/arc_shape.dart rename to packages/pinball_flame/lib/src/shapes/arc_shape.dart index 8b31e223..780fa2d3 100644 --- a/packages/pinball_components/lib/src/components/shapes/arc_shape.dart +++ b/packages/pinball_flame/lib/src/shapes/arc_shape.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:geometry/geometry.dart'; @@ -28,7 +26,6 @@ class ArcShape extends ChainShape { final Vector2 center; /// The radius of the arc. - // TODO(alestiago): Check if modifying the parent radius makes sense. final double arcRadius; /// Specifies the size of the arc, in radians. @@ -38,17 +35,4 @@ class ArcShape extends ChainShape { /// Angle in radians to rotate the arc around its [center]. final double rotation; - - ArcShape copyWith({ - Vector2? center, - double? arcRadius, - double? angle, - double? rotation, - }) => - ArcShape( - center: center ?? this.center, - arcRadius: arcRadius ?? this.arcRadius, - angle: angle ?? this.angle, - rotation: rotation ?? this.rotation, - ); } diff --git a/packages/pinball_components/lib/src/components/shapes/bezier_curve_shape.dart b/packages/pinball_flame/lib/src/shapes/bezier_curve_shape.dart similarity index 71% rename from packages/pinball_components/lib/src/components/shapes/bezier_curve_shape.dart rename to packages/pinball_flame/lib/src/shapes/bezier_curve_shape.dart index 0b4e8fef..00d1bafb 100644 --- a/packages/pinball_components/lib/src/components/shapes/bezier_curve_shape.dart +++ b/packages/pinball_flame/lib/src/shapes/bezier_curve_shape.dart @@ -1,6 +1,3 @@ -// ignore_for_file: public_member_api_docs - -import 'package:flame/extensions.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:geometry/geometry.dart'; @@ -20,9 +17,4 @@ class BezierCurveShape extends ChainShape { /// First and last [controlPoints] set the beginning and end of the curve, /// inner points between them set its final shape. final List controlPoints; - - /// Rotates the bezier curve by a given [angle] in radians. - void rotate(double angle) { - vertices.map((vector) => vector..rotate(angle)).toList(); - } } diff --git a/packages/pinball_components/lib/src/components/shapes/ellipse_shape.dart b/packages/pinball_flame/lib/src/shapes/ellipse_shape.dart similarity index 67% rename from packages/pinball_components/lib/src/components/shapes/ellipse_shape.dart rename to packages/pinball_flame/lib/src/shapes/ellipse_shape.dart index bfa7d435..5c523a3f 100644 --- a/packages/pinball_components/lib/src/components/shapes/ellipse_shape.dart +++ b/packages/pinball_flame/lib/src/shapes/ellipse_shape.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flame/extensions.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:geometry/geometry.dart'; @@ -26,7 +24,6 @@ class EllipseShape extends ChainShape { /// The top left corner of the ellipse. /// /// Where the initial painting begins. - // TODO(ruialonso): Change to use appropiate center. final Vector2 center; /// Major radius is specified by [majorRadius]. @@ -37,17 +34,8 @@ class EllipseShape extends ChainShape { /// Rotates the ellipse by a given [angle] in radians. void rotate(double angle) { - vertices.map((vector) => vector..rotate(angle)).toList(); + for (final vector in vertices) { + vector.rotate(angle); + } } - - EllipseShape copyWith({ - Vector2? center, - double? majorRadius, - double? minorRadius, - }) => - EllipseShape( - center: center ?? this.center, - majorRadius: majorRadius ?? this.majorRadius, - minorRadius: minorRadius ?? this.minorRadius, - ); } diff --git a/packages/pinball_components/lib/src/components/shapes/shapes.dart b/packages/pinball_flame/lib/src/shapes/shapes.dart similarity index 100% rename from packages/pinball_components/lib/src/components/shapes/shapes.dart rename to packages/pinball_flame/lib/src/shapes/shapes.dart diff --git a/packages/pinball_flame/lib/src/sprite_animation.dart b/packages/pinball_flame/lib/src/sprite_animation.dart index 2990fb14..60fd1c8b 100644 --- a/packages/pinball_flame/lib/src/sprite_animation.dart +++ b/packages/pinball_flame/lib/src/sprite_animation.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:math'; import 'package:flame/components.dart'; @@ -9,8 +7,6 @@ import 'package:flutter/material.dart' hide Animation; /// {@template flame.widgets.sprite_animation_widget} /// A [StatelessWidget] that renders a [SpriteAnimation]. /// {@endtemplate} -// TODO(arturplaczek): Remove when this PR will be merged. -// https://github.com/flame-engine/flame/pull/1552 class SpriteAnimationWidget extends StatelessWidget { /// {@macro flame.widgets.sprite_animation_widget} const SpriteAnimationWidget({ @@ -22,6 +18,7 @@ class SpriteAnimationWidget extends StatelessWidget { /// The positioning [Anchor]. final Anchor anchor; + /// Controller in charge of the sprite animations final SpriteAnimationController controller; @override @@ -40,7 +37,11 @@ class SpriteAnimationWidget extends StatelessWidget { } } +/// {@template sprite_animation_controller} +/// Custom [AnimationController] that manages sprite assets +/// {@endtemplate} class SpriteAnimationController extends AnimationController { + /// {@macro sprite_animation_controller} SpriteAnimationController({ required TickerProvider vsync, required this.animation, @@ -48,6 +49,7 @@ class SpriteAnimationController extends AnimationController { duration = Duration(seconds: animation.totalDuration().ceil()); } + /// [SpriteAnimation] associated to this controller final SpriteAnimation animation; double? _lastUpdated; @@ -63,7 +65,11 @@ class SpriteAnimationController extends AnimationController { } } +/// {@template sprite_painter} +/// [CustomPainter] specialized in [Sprite] assets. +/// {@endtemplate} class SpritePainter extends CustomPainter { + /// {@macro sprite_painter} SpritePainter( this._sprite, this._anchor, { diff --git a/packages/pinball_flame/pubspec.yaml b/packages/pinball_flame/pubspec.yaml index 4639a080..327951a1 100644 --- a/packages/pinball_flame/pubspec.yaml +++ b/packages/pinball_flame/pubspec.yaml @@ -17,6 +17,8 @@ dependencies: ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f flutter: sdk: flutter + geometry: + path: ../geometry dev_dependencies: flame_test: ^1.3.0 diff --git a/packages/pinball_components/test/src/components/layer_test.dart b/packages/pinball_flame/test/layer_test.dart similarity index 82% rename from packages/pinball_components/test/src/components/layer_test.dart rename to packages/pinball_flame/test/layer_test.dart index d47702ea..93ab29a6 100644 --- a/packages/pinball_components/test/src/components/layer_test.dart +++ b/packages/pinball_flame/test/layer_test.dart @@ -4,9 +4,9 @@ import 'dart:math' as math; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; -class TestLayeredBodyComponent extends BodyComponent with Layered { +class _TestLayeredBodyComponent extends BodyComponent with Layered { @override Body createBody() { final fixtureDef = FixtureDef(CircleShape()); @@ -14,7 +14,7 @@ class TestLayeredBodyComponent extends BodyComponent with Layered { } } -class TestBodyComponent extends BodyComponent { +class _TestBodyComponent extends BodyComponent { @override Body createBody() { final fixtureDef = FixtureDef(CircleShape()); @@ -41,12 +41,12 @@ void main() { } flameTester.test('TestBodyComponent has fixtures', (game) async { - final component = TestBodyComponent(); + final component = _TestBodyComponent(); await game.ensureAdd(component); }); test('correctly sets and gets', () { - final component = TestLayeredBodyComponent() + final component = _TestLayeredBodyComponent() ..layer = Layer.spaceshipEntranceRamp; expect(component.layer, Layer.spaceshipEntranceRamp); }); @@ -55,7 +55,7 @@ void main() { 'layers correctly before being loaded', (game) async { const expectedLayer = Layer.spaceshipEntranceRamp; - final component = TestLayeredBodyComponent()..layer = expectedLayer; + final component = _TestLayeredBodyComponent()..layer = expectedLayer; await game.ensureAdd(component); _expectLayerOnFixtures( @@ -70,7 +70,7 @@ void main() { 'when multiple different sets', (game) async { const expectedLayer = Layer.launcher; - final component = TestLayeredBodyComponent() + final component = _TestLayeredBodyComponent() ..layer = Layer.spaceshipEntranceRamp; expect(component.layer, isNot(equals(expectedLayer))); @@ -89,7 +89,7 @@ void main() { 'layers correctly after being loaded', (game) async { const expectedLayer = Layer.spaceshipEntranceRamp; - final component = TestLayeredBodyComponent(); + final component = _TestLayeredBodyComponent(); await game.ensureAdd(component); component.layer = expectedLayer; _expectLayerOnFixtures( @@ -104,7 +104,7 @@ void main() { 'when multiple different sets', (game) async { const expectedLayer = Layer.launcher; - final component = TestLayeredBodyComponent(); + final component = _TestLayeredBodyComponent(); await game.ensureAdd(component); component.layer = Layer.spaceshipEntranceRamp; @@ -122,7 +122,7 @@ void main() { 'defaults to Layer.all ' 'when no layer is given', (game) async { - final component = TestLayeredBodyComponent(); + final component = _TestLayeredBodyComponent(); await game.ensureAdd(component); expect(component.layer, equals(Layer.all)); }, @@ -134,15 +134,18 @@ void main() { const parentLayer = Layer.spaceshipEntranceRamp; const childLayer = Layer.board; - final component = TestLayeredBodyComponent()..layer = parentLayer; - final childComponent = TestLayeredBodyComponent()..layer = childLayer; + final component = _TestLayeredBodyComponent()..layer = parentLayer; + final childComponent = _TestLayeredBodyComponent()..layer = childLayer; await component.add(childComponent); await game.ensureAdd(component); expect(childLayer, isNot(equals(parentLayer))); for (final child in component.children) { - expect((child as TestLayeredBodyComponent).layer, equals(childLayer)); + expect( + (child as _TestLayeredBodyComponent).layer, + equals(childLayer), + ); } }, ); @@ -152,15 +155,15 @@ void main() { (game) async { const parentLayer = Layer.spaceshipEntranceRamp; - final component = TestLayeredBodyComponent()..layer = parentLayer; - final childComponent = TestBodyComponent(); + final component = _TestLayeredBodyComponent()..layer = parentLayer; + final childComponent = _TestBodyComponent(); await component.add(childComponent); await game.ensureAdd(component); for (final child in component.children) { expect( - (child as TestBodyComponent) + (child as _TestBodyComponent) .body .fixtures .first diff --git a/packages/pinball_flame/test/src/contact_behavior_test.dart b/packages/pinball_flame/test/src/behaviors/contact_behavior_test.dart similarity index 100% rename from packages/pinball_flame/test/src/contact_behavior_test.dart rename to packages/pinball_flame/test/src/behaviors/contact_behavior_test.dart diff --git a/packages/pinball_flame/test/src/behaviors/layer_contact_behavior_test.dart b/packages/pinball_flame/test/src/behaviors/layer_contact_behavior_test.dart new file mode 100644 index 00000000..d4b7ba18 --- /dev/null +++ b/packages/pinball_flame/test/src/behaviors/layer_contact_behavior_test.dart @@ -0,0 +1,78 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class _TestBodyComponent extends BodyComponent { + @override + Body createBody() { + final shape = CircleShape()..radius = 1; + return world.createBody(BodyDef())..createFixtureFromShape(shape); + } +} + +class _TestLayeredBodyComponent extends _TestBodyComponent with Layered { + _TestLayeredBodyComponent({required Layer layer}) { + layer = layer; + } +} + +class _MockContact extends Mock implements Contact {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(Forge2DGame.new); + + group('LayerContactBehavior', () { + test('can be instantiated', () { + expect( + LayerContactBehavior(layer: Layer.all), + isA(), + ); + }); + + flameTester.test('can be loaded', (game) async { + final behavior = LayerContactBehavior(layer: Layer.all); + final parent = _TestBodyComponent(); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + expect(parent.children, contains(behavior)); + }); + + flameTester.test('beginContact changes layer', (game) async { + const oldLayer = Layer.all; + const newLayer = Layer.board; + final behavior = LayerContactBehavior(layer: newLayer); + final parent = _TestBodyComponent(); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + + final component = _TestLayeredBodyComponent(layer: oldLayer); + + behavior.beginContact(component, _MockContact()); + + expect(component.layer, newLayer); + }); + + flameTester.test('endContact changes layer', (game) async { + const oldLayer = Layer.all; + const newLayer = Layer.board; + final behavior = LayerContactBehavior( + layer: newLayer, + onBegin: false, + ); + final parent = _TestBodyComponent(); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + + final component = _TestLayeredBodyComponent(layer: oldLayer); + + behavior.endContact(component, _MockContact()); + + expect(component.layer, newLayer); + }); + }); +} diff --git a/packages/pinball_flame/test/src/behaviors/z_index_contact_behavior_test.dart b/packages/pinball_flame/test/src/behaviors/z_index_contact_behavior_test.dart new file mode 100644 index 00000000..292a51fc --- /dev/null +++ b/packages/pinball_flame/test/src/behaviors/z_index_contact_behavior_test.dart @@ -0,0 +1,75 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class _TestBodyComponent extends BodyComponent { + @override + Body createBody() { + final shape = CircleShape()..radius = 1; + return world.createBody(BodyDef())..createFixtureFromShape(shape); + } +} + +class _TestZIndexBodyComponent extends _TestBodyComponent with ZIndex { + _TestZIndexBodyComponent({required int zIndex}) { + zIndex = zIndex; + } +} + +class _MockContact extends Mock implements Contact {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(Forge2DGame.new); + + group('ZIndexContactBehavior', () { + test('can be instantiated', () { + expect( + ZIndexContactBehavior(zIndex: 0), + isA(), + ); + }); + + flameTester.test('can be loaded', (game) async { + final behavior = ZIndexContactBehavior(zIndex: 0); + final parent = _TestBodyComponent(); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + expect(parent.children, contains(behavior)); + }); + + flameTester.test('beginContact changes zIndex', (game) async { + const oldIndex = 0; + const newIndex = 1; + final behavior = ZIndexContactBehavior(zIndex: newIndex); + final parent = _TestBodyComponent(); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + + final component = _TestZIndexBodyComponent(zIndex: oldIndex); + + behavior.beginContact(component, _MockContact()); + + expect(component.zIndex, newIndex); + }); + + flameTester.test('endContact changes zIndex', (game) async { + const oldIndex = 0; + const newIndex = 1; + final behavior = ZIndexContactBehavior(zIndex: newIndex, onBegin: false); + final parent = _TestBodyComponent(); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + + final component = _TestZIndexBodyComponent(zIndex: oldIndex); + + behavior.endContact(component, _MockContact()); + + expect(component.zIndex, newIndex); + }); + }); +} diff --git a/packages/pinball_flame/test/src/canvas/canvas_component_test.dart b/packages/pinball_flame/test/src/canvas/canvas_component_test.dart index 7bf7fd88..2ad45b6c 100644 --- a/packages/pinball_flame/test/src/canvas/canvas_component_test.dart +++ b/packages/pinball_flame/test/src/canvas/canvas_component_test.dart @@ -50,7 +50,7 @@ void main() { ); flameTester.testGameWidget( - 'calls onSpritePainted when paiting a sprite', + 'calls onSpritePainted when painting a sprite', setUp: (game, tester) async { final spriteComponent = _TestSpriteComponent(); diff --git a/packages/pinball_flame/test/src/component_controller_test.dart b/packages/pinball_flame/test/src/component_controller_test.dart index 0e08be92..addcf2b0 100644 --- a/packages/pinball_flame/test/src/component_controller_test.dart +++ b/packages/pinball_flame/test/src/component_controller_test.dart @@ -38,9 +38,9 @@ void main() { final component = Component(); final controller = TestComponentController(component); - final anotherComponet = Component(); + final anotherComponent = Component(); await expectLater( - () async => await anotherComponet.add(controller), + () async => await anotherComponent.add(controller), throwsAssertionError, ); }, diff --git a/packages/pinball_flame/test/src/keyboard_input_controller_test.dart b/packages/pinball_flame/test/src/keyboard_input_controller_test.dart index 7b554e8c..f3c783ad 100644 --- a/packages/pinball_flame/test/src/keyboard_input_controller_test.dart +++ b/packages/pinball_flame/test/src/keyboard_input_controller_test.dart @@ -1,11 +1,36 @@ // ignore_for_file: cascade_invocations, one_member_abstracts +import 'package:flame/game.dart'; +import 'package:flame_test/flame_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball_flame/pinball_flame.dart'; +class _TestGame extends FlameGame { + bool pressed = false; + + @override + Future? onLoad() async { + await super.onLoad(); + + await add( + KeyboardInputController( + keyUp: { + LogicalKeyboardKey.enter: () { + pressed = true; + return true; + }, + LogicalKeyboardKey.escape: () { + return false; + }, + }, + ), + ); + } +} + abstract class _KeyCall { bool onCall(); } @@ -75,4 +100,15 @@ void main() { }, ); }); + group('VirtualKeyEvents', () { + final flameTester = FlameTester(_TestGame.new); + + group('onVirtualKeyUp', () { + flameTester.test('triggers the event', (game) async { + await game.ready(); + game.triggerVirtualKeyUp(LogicalKeyboardKey.enter); + expect(game.pressed, isTrue); + }); + }); + }); } diff --git a/packages/pinball_flame/test/src/pinball_forge2d_game_test.dart b/packages/pinball_flame/test/src/pinball_forge2d_game_test.dart index b8dc9fcc..2da32f0e 100644 --- a/packages/pinball_flame/test/src/pinball_forge2d_game_test.dart +++ b/packages/pinball_flame/test/src/pinball_forge2d_game_test.dart @@ -19,7 +19,7 @@ void main() { }); flameTester.test( - 'screenToFlameWorld throws UnimpelementedError', + 'screenToFlameWorld throws UnimplementedError', (game) async { expect( () => game.screenToFlameWorld(Vector2.zero()), @@ -29,7 +29,7 @@ void main() { ); flameTester.test( - 'screenToWorld throws UnimpelementedError', + 'screenToWorld throws UnimplementedError', (game) async { expect( () => game.screenToWorld(Vector2.zero()), @@ -39,7 +39,7 @@ void main() { ); flameTester.test( - 'worldToScreen throws UnimpelementedError', + 'worldToScreen throws UnimplementedError', (game) async { expect( () => game.worldToScreen(Vector2.zero()), diff --git a/packages/pinball_flame/test/src/shapes/arc_shape_test.dart b/packages/pinball_flame/test/src/shapes/arc_shape_test.dart new file mode 100644 index 00000000..0c9f0a0f --- /dev/null +++ b/packages/pinball_flame/test/src/shapes/arc_shape_test.dart @@ -0,0 +1,20 @@ +import 'dart:math' as math; +import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +void main() { + group('ArcShape', () { + test('can be instantiated', () { + expect( + ArcShape( + center: Vector2.zero(), + arcRadius: 10, + angle: 2 * math.pi, + ), + isA(), + ); + }); + }); +} diff --git a/packages/pinball_flame/test/src/shapes/bezier_curve_shape_test.dart b/packages/pinball_flame/test/src/shapes/bezier_curve_shape_test.dart new file mode 100644 index 00000000..c2328a2f --- /dev/null +++ b/packages/pinball_flame/test/src/shapes/bezier_curve_shape_test.dart @@ -0,0 +1,22 @@ +import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +void main() { + group('BezierCurveShape', () { + test('can be instantiated', () { + expect( + BezierCurveShape( + controlPoints: [ + Vector2(0, 0), + Vector2(10, 0), + Vector2(0, 10), + Vector2(10, 10), + ], + ), + isA(), + ); + }); + }); +} diff --git a/packages/pinball_flame/test/src/shapes/ellipse_shape_test.dart b/packages/pinball_flame/test/src/shapes/ellipse_shape_test.dart new file mode 100644 index 00000000..0bb760d9 --- /dev/null +++ b/packages/pinball_flame/test/src/shapes/ellipse_shape_test.dart @@ -0,0 +1,41 @@ +import 'dart:math' as math; +import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +void main() { + group('EllipseShape', () { + test('can be instantiated', () { + expect( + EllipseShape( + center: Vector2.zero(), + majorRadius: 10, + minorRadius: 8, + ), + isA(), + ); + }); + + test('rotate returns vertices rotated', () { + const rotationAngle = 2 * math.pi; + final ellipseShape = EllipseShape( + center: Vector2.zero(), + majorRadius: 10, + minorRadius: 8, + ); + final ellipseShapeRotated = EllipseShape( + center: Vector2.zero(), + majorRadius: 10, + minorRadius: 8, + )..rotate(rotationAngle); + + for (var index = 0; index < ellipseShape.vertices.length; index++) { + expect( + ellipseShape.vertices[index]..rotate(rotationAngle), + equals(ellipseShapeRotated.vertices[index]), + ); + } + }); + }); +} diff --git a/packages/pinball_flame/test/src/sprite_animation_test.dart b/packages/pinball_flame/test/src/sprite_animation_test.dart index dc37d983..5483ee6e 100644 --- a/packages/pinball_flame/test/src/sprite_animation_test.dart +++ b/packages/pinball_flame/test/src/sprite_animation_test.dart @@ -10,9 +10,6 @@ class _MockSpriteAnimation extends Mock implements SpriteAnimation {} class _MockSprite extends Mock implements Sprite {} -// TODO(arturplaczek): Remove when this PR will be merged. -// https://github.com/flame-engine/flame/pull/1552 - void main() { group('PinballSpriteAnimationWidget', () { late SpriteAnimationController controller; diff --git a/packages/pinball_theme/assets/images/android/animation.png b/packages/pinball_theme/assets/images/android/animation.png index fc7465be..47f5f74c 100644 Binary files a/packages/pinball_theme/assets/images/android/animation.png and b/packages/pinball_theme/assets/images/android/animation.png differ diff --git a/packages/pinball_theme/assets/images/android/background.jpg b/packages/pinball_theme/assets/images/android/background.jpg new file mode 100644 index 00000000..bb1a6992 Binary files /dev/null and b/packages/pinball_theme/assets/images/android/background.jpg differ diff --git a/packages/pinball_theme/assets/images/android/background.png b/packages/pinball_theme/assets/images/android/background.png deleted file mode 100644 index f751dcdc..00000000 Binary files a/packages/pinball_theme/assets/images/android/background.png and /dev/null differ diff --git a/packages/pinball_theme/assets/images/android/ball.png b/packages/pinball_theme/assets/images/android/ball.png index b5cfbc3f..8310c098 100644 Binary files a/packages/pinball_theme/assets/images/android/ball.png and b/packages/pinball_theme/assets/images/android/ball.png differ diff --git a/packages/pinball_theme/assets/images/android/icon.png b/packages/pinball_theme/assets/images/android/icon.png index ff365ffe..03857b0a 100644 Binary files a/packages/pinball_theme/assets/images/android/icon.png and b/packages/pinball_theme/assets/images/android/icon.png differ diff --git a/packages/pinball_theme/assets/images/android/leaderboard_icon.png b/packages/pinball_theme/assets/images/android/leaderboard_icon.png index 238e29ef..ae97c412 100644 Binary files a/packages/pinball_theme/assets/images/android/leaderboard_icon.png and b/packages/pinball_theme/assets/images/android/leaderboard_icon.png differ diff --git a/packages/pinball_theme/assets/images/dash/animation.png b/packages/pinball_theme/assets/images/dash/animation.png index e812415f..97d4fb07 100644 Binary files a/packages/pinball_theme/assets/images/dash/animation.png and b/packages/pinball_theme/assets/images/dash/animation.png differ diff --git a/packages/pinball_theme/assets/images/dash/background.jpg b/packages/pinball_theme/assets/images/dash/background.jpg new file mode 100644 index 00000000..667454f4 Binary files /dev/null and b/packages/pinball_theme/assets/images/dash/background.jpg differ diff --git a/packages/pinball_theme/assets/images/dash/background.png b/packages/pinball_theme/assets/images/dash/background.png deleted file mode 100644 index a36601c9..00000000 Binary files a/packages/pinball_theme/assets/images/dash/background.png and /dev/null differ diff --git a/packages/pinball_theme/assets/images/dash/ball.png b/packages/pinball_theme/assets/images/dash/ball.png index fa754cbc..f93023f6 100644 Binary files a/packages/pinball_theme/assets/images/dash/ball.png and b/packages/pinball_theme/assets/images/dash/ball.png differ diff --git a/packages/pinball_theme/assets/images/dash/icon.png b/packages/pinball_theme/assets/images/dash/icon.png index 45bba327..0df550ea 100644 Binary files a/packages/pinball_theme/assets/images/dash/icon.png and b/packages/pinball_theme/assets/images/dash/icon.png differ diff --git a/packages/pinball_theme/assets/images/dash/leaderboard_icon.png b/packages/pinball_theme/assets/images/dash/leaderboard_icon.png index 5c172d47..69b2919c 100644 Binary files a/packages/pinball_theme/assets/images/dash/leaderboard_icon.png and b/packages/pinball_theme/assets/images/dash/leaderboard_icon.png differ diff --git a/packages/pinball_theme/assets/images/dino/animation.png b/packages/pinball_theme/assets/images/dino/animation.png index c75b16f9..de7e1b60 100644 Binary files a/packages/pinball_theme/assets/images/dino/animation.png and b/packages/pinball_theme/assets/images/dino/animation.png differ diff --git a/packages/pinball_theme/assets/images/dino/background.jpg b/packages/pinball_theme/assets/images/dino/background.jpg new file mode 100644 index 00000000..6cf0626e Binary files /dev/null and b/packages/pinball_theme/assets/images/dino/background.jpg differ diff --git a/packages/pinball_theme/assets/images/dino/background.png b/packages/pinball_theme/assets/images/dino/background.png deleted file mode 100644 index e42f1705..00000000 Binary files a/packages/pinball_theme/assets/images/dino/background.png and /dev/null differ diff --git a/packages/pinball_theme/assets/images/dino/ball.png b/packages/pinball_theme/assets/images/dino/ball.png index 02b99c43..7c1885a2 100644 Binary files a/packages/pinball_theme/assets/images/dino/ball.png and b/packages/pinball_theme/assets/images/dino/ball.png differ diff --git a/packages/pinball_theme/assets/images/dino/icon.png b/packages/pinball_theme/assets/images/dino/icon.png index 0114060e..28682c73 100644 Binary files a/packages/pinball_theme/assets/images/dino/icon.png and b/packages/pinball_theme/assets/images/dino/icon.png differ diff --git a/packages/pinball_theme/assets/images/dino/leaderboard_icon.png b/packages/pinball_theme/assets/images/dino/leaderboard_icon.png index b1033371..4a230b39 100644 Binary files a/packages/pinball_theme/assets/images/dino/leaderboard_icon.png and b/packages/pinball_theme/assets/images/dino/leaderboard_icon.png differ diff --git a/packages/pinball_theme/assets/images/pinball_button.png b/packages/pinball_theme/assets/images/pinball_button.png index 62373b85..8d10ec7c 100644 Binary files a/packages/pinball_theme/assets/images/pinball_button.png and b/packages/pinball_theme/assets/images/pinball_button.png differ diff --git a/packages/pinball_theme/assets/images/select_character_background.png b/packages/pinball_theme/assets/images/select_character_background.png index 69120148..7ba7f17f 100644 Binary files a/packages/pinball_theme/assets/images/select_character_background.png and b/packages/pinball_theme/assets/images/select_character_background.png differ diff --git a/packages/pinball_theme/assets/images/sparky/animation.png b/packages/pinball_theme/assets/images/sparky/animation.png index 1aff4772..ce51eb0d 100644 Binary files a/packages/pinball_theme/assets/images/sparky/animation.png and b/packages/pinball_theme/assets/images/sparky/animation.png differ diff --git a/packages/pinball_theme/assets/images/sparky/background.jpg b/packages/pinball_theme/assets/images/sparky/background.jpg new file mode 100644 index 00000000..08ad2dba Binary files /dev/null and b/packages/pinball_theme/assets/images/sparky/background.jpg differ diff --git a/packages/pinball_theme/assets/images/sparky/background.png b/packages/pinball_theme/assets/images/sparky/background.png deleted file mode 100644 index 5044376c..00000000 Binary files a/packages/pinball_theme/assets/images/sparky/background.png and /dev/null differ diff --git a/packages/pinball_theme/assets/images/sparky/ball.png b/packages/pinball_theme/assets/images/sparky/ball.png index 95e5a10b..7fd20368 100644 Binary files a/packages/pinball_theme/assets/images/sparky/ball.png and b/packages/pinball_theme/assets/images/sparky/ball.png differ diff --git a/packages/pinball_theme/assets/images/sparky/icon.png b/packages/pinball_theme/assets/images/sparky/icon.png index 4e484438..46b8dfa3 100644 Binary files a/packages/pinball_theme/assets/images/sparky/icon.png and b/packages/pinball_theme/assets/images/sparky/icon.png differ diff --git a/packages/pinball_theme/assets/images/sparky/leaderboard_icon.png b/packages/pinball_theme/assets/images/sparky/leaderboard_icon.png index 76001516..bd5e56e1 100644 Binary files a/packages/pinball_theme/assets/images/sparky/leaderboard_icon.png and b/packages/pinball_theme/assets/images/sparky/leaderboard_icon.png differ diff --git a/packages/pinball_theme/lib/src/generated/assets.gen.dart b/packages/pinball_theme/lib/src/generated/assets.gen.dart index 545f514b..17ec4caf 100644 --- a/packages/pinball_theme/lib/src/generated/assets.gen.dart +++ b/packages/pinball_theme/lib/src/generated/assets.gen.dart @@ -32,9 +32,9 @@ class $AssetsImagesAndroidGen { AssetGenImage get animation => const AssetGenImage('assets/images/android/animation.png'); - /// File path: assets/images/android/background.png + /// File path: assets/images/android/background.jpg AssetGenImage get background => - const AssetGenImage('assets/images/android/background.png'); + const AssetGenImage('assets/images/android/background.jpg'); /// File path: assets/images/android/ball.png AssetGenImage get ball => @@ -56,9 +56,9 @@ class $AssetsImagesDashGen { AssetGenImage get animation => const AssetGenImage('assets/images/dash/animation.png'); - /// File path: assets/images/dash/background.png + /// File path: assets/images/dash/background.jpg AssetGenImage get background => - const AssetGenImage('assets/images/dash/background.png'); + const AssetGenImage('assets/images/dash/background.jpg'); /// File path: assets/images/dash/ball.png AssetGenImage get ball => const AssetGenImage('assets/images/dash/ball.png'); @@ -78,9 +78,9 @@ class $AssetsImagesDinoGen { AssetGenImage get animation => const AssetGenImage('assets/images/dino/animation.png'); - /// File path: assets/images/dino/background.png + /// File path: assets/images/dino/background.jpg AssetGenImage get background => - const AssetGenImage('assets/images/dino/background.png'); + const AssetGenImage('assets/images/dino/background.jpg'); /// File path: assets/images/dino/ball.png AssetGenImage get ball => const AssetGenImage('assets/images/dino/ball.png'); @@ -100,9 +100,9 @@ class $AssetsImagesSparkyGen { AssetGenImage get animation => const AssetGenImage('assets/images/sparky/animation.png'); - /// File path: assets/images/sparky/background.png + /// File path: assets/images/sparky/background.jpg AssetGenImage get background => - const AssetGenImage('assets/images/sparky/background.png'); + const AssetGenImage('assets/images/sparky/background.jpg'); /// File path: assets/images/sparky/ball.png AssetGenImage get ball => diff --git a/packages/pinball_ui/assets/images/button/dpad_down.png b/packages/pinball_ui/assets/images/button/dpad_down.png new file mode 100644 index 00000000..71c4b0a7 Binary files /dev/null and b/packages/pinball_ui/assets/images/button/dpad_down.png differ diff --git a/packages/pinball_ui/assets/images/button/dpad_left.png b/packages/pinball_ui/assets/images/button/dpad_left.png new file mode 100644 index 00000000..a0979677 Binary files /dev/null and b/packages/pinball_ui/assets/images/button/dpad_left.png differ diff --git a/packages/pinball_ui/assets/images/button/dpad_right.png b/packages/pinball_ui/assets/images/button/dpad_right.png new file mode 100644 index 00000000..9e7a2be9 Binary files /dev/null and b/packages/pinball_ui/assets/images/button/dpad_right.png differ diff --git a/packages/pinball_ui/assets/images/button/dpad_up.png b/packages/pinball_ui/assets/images/button/dpad_up.png new file mode 100644 index 00000000..6113e3de Binary files /dev/null and b/packages/pinball_ui/assets/images/button/dpad_up.png differ diff --git a/packages/pinball_ui/assets/images/button/pinball_button.png b/packages/pinball_ui/assets/images/button/pinball_button.png index 62373b85..8d10ec7c 100644 Binary files a/packages/pinball_ui/assets/images/button/pinball_button.png and b/packages/pinball_ui/assets/images/button/pinball_button.png differ diff --git a/packages/pinball_ui/assets/images/dialog/background.png b/packages/pinball_ui/assets/images/dialog/background.png index 0aad300f..82b3cb58 100644 Binary files a/packages/pinball_ui/assets/images/dialog/background.png and b/packages/pinball_ui/assets/images/dialog/background.png differ diff --git a/packages/pinball_ui/lib/gen/assets.gen.dart b/packages/pinball_ui/lib/gen/assets.gen.dart index 8972e8e0..9b09b254 100644 --- a/packages/pinball_ui/lib/gen/assets.gen.dart +++ b/packages/pinball_ui/lib/gen/assets.gen.dart @@ -3,8 +3,6 @@ /// FlutterGen /// ***************************************************** -// ignore_for_file: directives_ordering,unnecessary_import - import 'package:flutter/widgets.dart'; class $AssetsImagesGen { @@ -17,7 +15,14 @@ class $AssetsImagesGen { class $AssetsImagesButtonGen { const $AssetsImagesButtonGen(); - /// File path: assets/images/button/pinball_button.png + AssetGenImage get dpadDown => + const AssetGenImage('assets/images/button/dpad_down.png'); + AssetGenImage get dpadLeft => + const AssetGenImage('assets/images/button/dpad_left.png'); + AssetGenImage get dpadRight => + const AssetGenImage('assets/images/button/dpad_right.png'); + AssetGenImage get dpadUp => + const AssetGenImage('assets/images/button/dpad_up.png'); AssetGenImage get pinballButton => const AssetGenImage('assets/images/button/pinball_button.png'); } @@ -25,7 +30,6 @@ class $AssetsImagesButtonGen { class $AssetsImagesDialogGen { const $AssetsImagesDialogGen(); - /// File path: assets/images/dialog/background.png AssetGenImage get background => const AssetGenImage('assets/images/dialog/background.png'); } diff --git a/packages/pinball_ui/lib/gen/fonts.gen.dart b/packages/pinball_ui/lib/gen/fonts.gen.dart index 5f77da16..b15f2dd0 100644 --- a/packages/pinball_ui/lib/gen/fonts.gen.dart +++ b/packages/pinball_ui/lib/gen/fonts.gen.dart @@ -3,14 +3,9 @@ /// FlutterGen /// ***************************************************** -// ignore_for_file: directives_ordering,unnecessary_import - class FontFamily { FontFamily._(); - /// Font family: PixeloidMono static const String pixeloidMono = 'PixeloidMono'; - - /// Font family: PixeloidSans static const String pixeloidSans = 'PixeloidSans'; } diff --git a/packages/pinball_ui/lib/src/theme/pinball_colors.dart b/packages/pinball_ui/lib/src/theme/pinball_colors.dart index d6029422..250db02c 100644 --- a/packages/pinball_ui/lib/src/theme/pinball_colors.dart +++ b/packages/pinball_ui/lib/src/theme/pinball_colors.dart @@ -1,17 +1,43 @@ -// ignore_for_file: public_member_api_docs import 'package:flutter/material.dart'; +/// Colors used in the application abstract class PinballColors { + /// Color: 0xFFFFFFFF static const Color white = Color(0xFFFFFFFF); + + /// Color: 0xFF000000 + static const Color black = Color(0xFF000000); + + /// Color: 0xFF0C32A4 static const Color darkBlue = Color(0xFF0C32A4); + + /// Color: 0xFFFFEE02 static const Color yellow = Color(0xFFFFEE02); + + /// Color: 0xFFE5AB05 static const Color orange = Color(0xFFE5AB05); + + /// Color: 0xFFF03939 static const Color red = Color(0xFFF03939); + + /// Color: 0xFF4B94F6 static const Color blue = Color(0xFF4B94F6); + + /// Color: 0x00000000 static const Color transparent = Color(0x00000000); + + /// Color: 0xFFE33B2D static const Color loadingDarkRed = Color(0xFFE33B2D); + + /// Color: 0xFFEC5E2B static const Color loadingLightRed = Color(0xFFEC5E2B); + + /// Color: 0xFF4087F8 static const Color loadingDarkBlue = Color(0xFF4087F8); + + /// Color: 0xFF6CCAE4 static const Color loadingLightBlue = Color(0xFF6CCAE4); + + /// Color: 0xFF274E54 static const Color crtBackground = Color(0xFF274E54); } diff --git a/packages/pinball_ui/lib/src/theme/pinball_text_style.dart b/packages/pinball_ui/lib/src/theme/pinball_text_style.dart index 18968680..8319fc1c 100644 --- a/packages/pinball_ui/lib/src/theme/pinball_text_style.dart +++ b/packages/pinball_ui/lib/src/theme/pinball_text_style.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:flutter/widgets.dart'; import 'package:pinball_ui/gen/fonts.gen.dart'; import 'package:pinball_ui/pinball_ui.dart'; @@ -7,7 +5,9 @@ import 'package:pinball_ui/pinball_ui.dart'; const _fontPackage = 'pinball_components'; const _primaryFontFamily = FontFamily.pixeloidSans; +/// Different [TextStyle] used in the game abstract class PinballTextStyle { + /// Font size: 28 | Color: white static const headline1 = TextStyle( fontSize: 28, package: _fontPackage, @@ -15,6 +15,7 @@ abstract class PinballTextStyle { color: PinballColors.white, ); + /// Font size: 24 | Color: white static const headline2 = TextStyle( fontSize: 24, package: _fontPackage, @@ -22,6 +23,7 @@ abstract class PinballTextStyle { color: PinballColors.white, ); + /// Font size: 20 | Color: darkBlue static const headline3 = TextStyle( color: PinballColors.darkBlue, fontSize: 20, @@ -30,6 +32,7 @@ abstract class PinballTextStyle { fontWeight: FontWeight.bold, ); + /// Font size: 16 | Color: white static const headline4 = TextStyle( color: PinballColors.white, fontSize: 16, @@ -37,6 +40,7 @@ abstract class PinballTextStyle { fontFamily: _primaryFontFamily, ); + /// Font size: 214| Color: white static const headline5 = TextStyle( color: PinballColors.white, fontSize: 14, @@ -44,13 +48,7 @@ abstract class PinballTextStyle { fontFamily: _primaryFontFamily, ); - static const subtitle2 = TextStyle( - color: PinballColors.white, - fontSize: 16, - package: _fontPackage, - fontFamily: _primaryFontFamily, - ); - + /// Font size: 12 | Color: white static const subtitle1 = TextStyle( fontSize: 12, fontFamily: _primaryFontFamily, diff --git a/packages/pinball_ui/lib/src/theme/pinball_theme.dart b/packages/pinball_ui/lib/src/theme/pinball_theme.dart index 43322391..cc08ec9d 100644 --- a/packages/pinball_ui/lib/src/theme/pinball_theme.dart +++ b/packages/pinball_ui/lib/src/theme/pinball_theme.dart @@ -18,7 +18,6 @@ class PinballTheme { headline4: PinballTextStyle.headline4, headline5: PinballTextStyle.headline5, subtitle1: PinballTextStyle.subtitle1, - subtitle2: PinballTextStyle.subtitle2, ); } } diff --git a/packages/pinball_ui/lib/src/widgets/animated_ellipsis_text.dart b/packages/pinball_ui/lib/src/widgets/animated_ellipsis_text.dart index 9b52d604..1c94cad7 100644 --- a/packages/pinball_ui/lib/src/widgets/animated_ellipsis_text.dart +++ b/packages/pinball_ui/lib/src/widgets/animated_ellipsis_text.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; -/// {@tempalte animated_ellipsis_text} +/// {@template animated_ellipsis_text} /// Every 500 milliseconds, it will add a new `.` at the end of the given /// [text]. Once 3 `.` have been added (e.g. `Loading...`), it will reset to /// zero ellipsis and start over again. diff --git a/packages/pinball_ui/lib/src/widgets/crt_background.dart b/packages/pinball_ui/lib/src/widgets/crt_background.dart index 202af1d3..6fcc22ee 100644 --- a/packages/pinball_ui/lib/src/widgets/crt_background.dart +++ b/packages/pinball_ui/lib/src/widgets/crt_background.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:pinball_ui/pinball_ui.dart'; /// {@template crt_background} -/// [BoxDecoration] that provides a CRT-like background efffect. +/// [BoxDecoration] that provides a CRT-like background effect. /// {@endtemplate} class CrtBackground extends BoxDecoration { /// {@macro crt_background} diff --git a/packages/pinball_ui/lib/src/widgets/pinball_dpad_button.dart b/packages/pinball_ui/lib/src/widgets/pinball_dpad_button.dart new file mode 100644 index 00000000..a0c3e653 --- /dev/null +++ b/packages/pinball_ui/lib/src/widgets/pinball_dpad_button.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:pinball_ui/gen/gen.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +/// Enum with all possible directions of a [PinballDpadButton]. +enum PinballDpadDirection { + /// Up + up, + + /// Down + down, + + /// Left + left, + + /// Right + right, +} + +extension _PinballDpadDirectionX on PinballDpadDirection { + String toAsset() { + switch (this) { + case PinballDpadDirection.up: + return Assets.images.button.dpadUp.keyName; + case PinballDpadDirection.down: + return Assets.images.button.dpadDown.keyName; + case PinballDpadDirection.left: + return Assets.images.button.dpadLeft.keyName; + case PinballDpadDirection.right: + return Assets.images.button.dpadRight.keyName; + } + } +} + +/// {@template pinball_dpad_button} +/// Widget that renders a Dpad button with a given direction. +/// {@endtemplate} +class PinballDpadButton extends StatelessWidget { + /// {@macro pinball_dpad_button} + const PinballDpadButton({ + Key? key, + required this.direction, + required this.onTap, + }) : super(key: key); + + /// Which [PinballDpadDirection] this button is. + final PinballDpadDirection direction; + + /// The function executed when the button is pressed. + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return Material( + color: PinballColors.transparent, + child: InkWell( + onTap: onTap, + child: Image.asset( + direction.toAsset(), + width: 60, + height: 60, + ), + ), + ); + } +} diff --git a/packages/pinball_ui/lib/src/widgets/widgets.dart b/packages/pinball_ui/lib/src/widgets/widgets.dart index 3aa96c3e..45a6daad 100644 --- a/packages/pinball_ui/lib/src/widgets/widgets.dart +++ b/packages/pinball_ui/lib/src/widgets/widgets.dart @@ -1,4 +1,5 @@ export 'animated_ellipsis_text.dart'; export 'crt_background.dart'; export 'pinball_button.dart'; +export 'pinball_dpad_button.dart'; export 'pinball_loading_indicator.dart'; diff --git a/packages/pinball_ui/test/src/theme/pinball_colors_test.dart b/packages/pinball_ui/test/src/theme/pinball_colors_test.dart index 469ab142..ddc95eda 100644 --- a/packages/pinball_ui/test/src/theme/pinball_colors_test.dart +++ b/packages/pinball_ui/test/src/theme/pinball_colors_test.dart @@ -8,6 +8,10 @@ void main() { expect(PinballColors.white, const Color(0xFFFFFFFF)); }); + test('black is 0xFF000000', () { + expect(PinballColors.black, const Color(0xFF000000)); + }); + test('darkBlue is 0xFF0C32A4', () { expect(PinballColors.darkBlue, const Color(0xFF0C32A4)); }); diff --git a/packages/pinball_ui/test/src/theme/pinball_text_style_test.dart b/packages/pinball_ui/test/src/theme/pinball_text_style_test.dart index 72cd66a6..ed1f1cb6 100644 --- a/packages/pinball_ui/test/src/theme/pinball_text_style_test.dart +++ b/packages/pinball_ui/test/src/theme/pinball_text_style_test.dart @@ -31,11 +31,5 @@ void main() { expect(style.fontSize, 12); expect(style.color, PinballColors.yellow); }); - - test('subtitle2 has fontSize 16 and white color', () { - const style = PinballTextStyle.subtitle2; - expect(style.fontSize, 16); - expect(style.color, PinballColors.white); - }); }); } diff --git a/packages/pinball_ui/test/src/theme/pinball_theme_test.dart b/packages/pinball_ui/test/src/theme/pinball_theme_test.dart index 915927f8..7eda2c1d 100644 --- a/packages/pinball_ui/test/src/theme/pinball_theme_test.dart +++ b/packages/pinball_ui/test/src/theme/pinball_theme_test.dart @@ -78,21 +78,6 @@ void main() { PinballTextStyle.subtitle1.fontFamily, ); }); - - test('subtitle2 matches PinballTextStyle#subtitle2', () { - expect( - PinballTheme.standard.textTheme.subtitle2!.fontSize, - PinballTextStyle.subtitle2.fontSize, - ); - expect( - PinballTheme.standard.textTheme.subtitle2!.color, - PinballTextStyle.subtitle2.color, - ); - expect( - PinballTheme.standard.textTheme.subtitle2!.fontFamily, - PinballTextStyle.subtitle2.fontFamily, - ); - }); }); }); } diff --git a/packages/pinball_ui/test/src/widgets/pinball_dpad_button_test.dart b/packages/pinball_ui/test/src/widgets/pinball_dpad_button_test.dart new file mode 100644 index 00000000..a7e89534 --- /dev/null +++ b/packages/pinball_ui/test/src/widgets/pinball_dpad_button_test.dart @@ -0,0 +1,122 @@ +// ignore_for_file: one_member_abstracts + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball_ui/gen/gen.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +extension _WidgetTesterX on WidgetTester { + Future pumpButton({ + required PinballDpadDirection direction, + required VoidCallback onTap, + }) async { + await pumpWidget( + MaterialApp( + home: Scaffold( + body: PinballDpadButton( + direction: direction, + onTap: onTap, + ), + ), + ), + ); + } +} + +extension _CommonFindersX on CommonFinders { + Finder byImagePath(String path) { + return find.byWidgetPredicate( + (widget) { + if (widget is Image) { + final image = widget.image; + + if (image is AssetImage) { + return image.keyName == path; + } + return false; + } + + return false; + }, + ); + } +} + +abstract class _VoidCallbackStubBase { + void onCall(); +} + +class _VoidCallbackStub extends Mock implements _VoidCallbackStubBase {} + +void main() { + group('PinballDpadButton', () { + testWidgets('can be tapped', (tester) async { + final stub = _VoidCallbackStub(); + await tester.pumpButton( + direction: PinballDpadDirection.up, + onTap: stub.onCall, + ); + + await tester.tap(find.byType(Image)); + + verify(stub.onCall).called(1); + }); + + group('up', () { + testWidgets('renders the correct image', (tester) async { + await tester.pumpButton( + direction: PinballDpadDirection.up, + onTap: () {}, + ); + + expect( + find.byImagePath(Assets.images.button.dpadUp.keyName), + findsOneWidget, + ); + }); + }); + + group('down', () { + testWidgets('renders the correct image', (tester) async { + await tester.pumpButton( + direction: PinballDpadDirection.down, + onTap: () {}, + ); + + expect( + find.byImagePath(Assets.images.button.dpadDown.keyName), + findsOneWidget, + ); + }); + }); + + group('left', () { + testWidgets('renders the correct image', (tester) async { + await tester.pumpButton( + direction: PinballDpadDirection.left, + onTap: () {}, + ); + + expect( + find.byImagePath(Assets.images.button.dpadLeft.keyName), + findsOneWidget, + ); + }); + }); + + group('right', () { + testWidgets('renders the correct image', (tester) async { + await tester.pumpButton( + direction: PinballDpadDirection.right, + onTap: () {}, + ); + + expect( + find.byImagePath(Assets.images.button.dpadRight.keyName), + findsOneWidget, + ); + }); + }); + }); +} diff --git a/packages/platform_helper/test/src/platform_helper_test.dart b/packages/platform_helper/test/src/platform_helper_test.dart index 69bec3a8..935bf082 100644 --- a/packages/platform_helper/test/src/platform_helper_test.dart +++ b/packages/platform_helper/test/src/platform_helper_test.dart @@ -27,7 +27,7 @@ void main() { }); test( - 'returns false when defaultTargetPlatform is niether iOS nor android', + 'returns false when defaultTargetPlatform is neither iOS nor android', () async { debugDefaultTargetPlatformOverride = TargetPlatform.macOS; expect(PlatformHelper().isMobile, isFalse); diff --git a/packages/share_repository/lib/src/share_repository.dart b/packages/share_repository/lib/src/share_repository.dart index 6e6679c2..16c29aee 100644 --- a/packages/share_repository/lib/src/share_repository.dart +++ b/packages/share_repository/lib/src/share_repository.dart @@ -11,6 +11,15 @@ class ShareRepository { final String _appUrl; + /// Url to the Github Open Source Pinball project. + static const openSourceCode = 'https://github.com/VGVentures/pinball'; + + /// Url to the Google IO Event. + static const googleIOEvent = 'https://events.google.com/io/'; + + /// Url to the Pinball game. + static const pinballGameUrl = 'https://ashehwkdkdjruejdnensjsjdne.web.app/#/'; + /// Returns a url to share the [value] on the given [platform]. /// /// The returned url can be opened using the [url_launcher](https://pub.dev/packages/url_launcher) package. diff --git a/pubspec.lock b/pubspec.lock index 83de1390..7199435a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -576,6 +576,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + share_repository: + dependency: "direct main" + description: + path: "packages/share_repository" + relative: true + source: path + version: "1.0.0+1" shelf: dependency: transitive description: @@ -694,7 +701,7 @@ packages: source: hosted version: "1.3.0" url_launcher: - dependency: transitive + dependency: "direct main" description: name: url_launcher url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 1a025d4a..7f925688 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,9 @@ dependencies: path: packages/pinball_ui platform_helper: path: packages/platform_helper + share_repository: + path: packages/share_repository + url_launcher: ^6.1.0 dev_dependencies: bloc_test: ^9.0.2 @@ -61,7 +64,7 @@ flutter: - assets/images/components/ - assets/images/bonus_animation/ - assets/images/score/ - - assets/images/link_box/ + - assets/images/loading_game/ flutter_gen: line_length: 80 diff --git a/test/app/view/app_test.dart b/test/app/view/app_test.dart index 4f04a89d..d247a562 100644 --- a/test/app/view/app_test.dart +++ b/test/app/view/app_test.dart @@ -5,26 +5,31 @@ import 'package:mocktail/mocktail.dart'; import 'package:pinball/app/app.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_audio/pinball_audio.dart'; +import 'package:share_repository/share_repository.dart'; class _MockAuthenticationRepository extends Mock implements AuthenticationRepository {} -class _MockPinballPlayer extends Mock implements PinballPlayer {} +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { } +class _MockShareRepository extends Mock implements ShareRepository {} + void main() { group('App', () { late AuthenticationRepository authenticationRepository; late LeaderboardRepository leaderboardRepository; - late PinballPlayer pinballPlayer; + late ShareRepository shareRepository; + late PinballAudioPlayer pinballAudioPlayer; setUp(() { authenticationRepository = _MockAuthenticationRepository(); leaderboardRepository = _MockLeaderboardRepository(); - pinballPlayer = _MockPinballPlayer(); - when(pinballPlayer.load).thenAnswer((_) => [Future.value()]); + shareRepository = _MockShareRepository(); + pinballAudioPlayer = _MockPinballAudioPlayer(); + when(pinballAudioPlayer.load).thenAnswer((_) => [Future.value()]); }); testWidgets('renders PinballGamePage', (tester) async { @@ -32,9 +37,11 @@ void main() { App( authenticationRepository: authenticationRepository, leaderboardRepository: leaderboardRepository, - pinballPlayer: pinballPlayer, + shareRepository: shareRepository, + pinballAudioPlayer: pinballAudioPlayer, ), ); + await tester.pump(const Duration(milliseconds: 1100)); expect(find.byType(PinballGamePage), findsOneWidget); }); }); diff --git a/test/assets_manager/cubit/assets_manager_cubit_test.dart b/test/assets_manager/cubit/assets_manager_cubit_test.dart deleted file mode 100644 index 27d9cedb..00000000 --- a/test/assets_manager/cubit/assets_manager_cubit_test.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'dart:async'; - -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball/assets_manager/assets_manager.dart'; - -void main() { - group('AssetsManagerCubit', () { - final completer1 = Completer(); - final completer2 = Completer(); - - final future1 = completer1.future; - final future2 = completer2.future; - - blocTest( - 'emits the loaded on the order that they load', - build: () => AssetsManagerCubit([future1, future2]), - act: (cubit) { - cubit.load(); - completer2.complete(); - completer1.complete(); - }, - expect: () => [ - AssetsManagerState( - loadables: [future1, future2], - loaded: [future2], - ), - AssetsManagerState( - loadables: [future1, future2], - loaded: [future2, future1], - ), - ], - ); - }); -} diff --git a/test/assets_manager/cubit/assets_manager_state_test.dart b/test/assets_manager/cubit/assets_manager_state_test.dart index 4882f880..41e94add 100644 --- a/test/assets_manager/cubit/assets_manager_state_test.dart +++ b/test/assets_manager/cubit/assets_manager_state_test.dart @@ -13,12 +13,11 @@ void main() { }); test('has the correct initial state', () { - final future = Future.value(); expect( - AssetsManagerState.initial(loadables: [future]), + AssetsManagerState.initial(), equals( AssetsManagerState( - loadables: [future], + loadables: const [], loaded: const [], ), ), diff --git a/test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart b/test/game/behaviors/ball_spawning_behavior_test.dart similarity index 90% rename from test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart rename to test/game/behaviors/ball_spawning_behavior_test.dart index f41487cd..d723c65e 100644 --- a/test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart +++ b/test/game/behaviors/ball_spawning_behavior_test.dart @@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/behaviors/ball_spawning_behavior.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; @@ -18,18 +19,20 @@ class _TestGame extends Forge2DGame { } Future pump( - Iterable children, { + List children, { GameBloc? gameBloc, }) async { await ensureAdd( - FlameBlocProvider.value( - value: gameBloc ?? GameBloc(), - children: [ - FlameProvider.value( - const theme.DashTheme(), - children: children, + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value( + value: gameBloc ?? GameBloc(), + ), + FlameBlocProvider.value( + value: CharacterThemeCubit(), ), ], + children: children, ), ); } diff --git a/test/game/behaviors/bonus_ball_spawning_behavior_test.dart b/test/game/behaviors/bonus_ball_spawning_behavior_test.dart new file mode 100644 index 00000000..1aacf506 --- /dev/null +++ b/test/game/behaviors/bonus_ball_spawning_behavior_test.dart @@ -0,0 +1,61 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/forge2d_game.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/select_character/select_character.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:pinball_theme/pinball_theme.dart' as theme; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + theme.Assets.images.dash.ball.keyName, + ]); + } + + Future pump(BonusBallSpawningBehavior child) async { + await ensureAdd( + FlameBlocProvider.value( + value: CharacterThemeCubit(), + children: [ + ZCanvasComponent( + children: [child], + ), + ], + ), + ); + } +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('FlutterForestBonusBehavior', () { + final flameTester = FlameTester(_TestGame.new); + + flameTester.test( + 'adds a ball with a BallImpulsingBehavior to the game onTick ' + 'resulting in a -40 x impulse', + (game) async { + await game.onLoad(); + final behavior = BonusBallSpawningBehavior(); + + await game.pump(behavior); + + game.update(behavior.timer.limit); + await game.ready(); + + final ball = game.descendants().whereType().single; + + expect(ball.body.linearVelocity.x, equals(-40)); + expect(ball.body.linearVelocity.y, equals(0)); + }, + ); + }); +} diff --git a/test/game/behaviors/bonus_noise_behavior_test.dart b/test/game/behaviors/bonus_noise_behavior_test.dart index 5ec37bce..b67659c0 100644 --- a/test/game/behaviors/bonus_noise_behavior_test.dart +++ b/test/game/behaviors/bonus_noise_behavior_test.dart @@ -14,15 +14,15 @@ import 'package:pinball_flame/pinball_flame.dart'; class _TestGame extends Forge2DGame { Future pump( BonusNoiseBehavior child, { - required PinballPlayer player, + required PinballAudioPlayer audioPlayer, required GameBloc bloc, }) { return ensureAdd( FlameBlocProvider.value( value: bloc, children: [ - FlameProvider.value( - player, + FlameProvider.value( + audioPlayer, children: [ child, ], @@ -33,7 +33,7 @@ class _TestGame extends Forge2DGame { } } -class _MockPinballPlayer extends Mock implements PinballPlayer {} +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} class _MockGameBloc extends Mock implements GameBloc {} @@ -41,7 +41,7 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('BonusNoiseBehavior', () { - late PinballPlayer player; + late PinballAudioPlayer audioPlayer; late GameBloc bloc; final flameTester = FlameTester(_TestGame.new); @@ -50,8 +50,8 @@ void main() { }); setUp(() { - player = _MockPinballPlayer(); - when(() => player.play(any())).thenAnswer((_) {}); + audioPlayer = _MockPinballAudioPlayer(); + when(() => audioPlayer.play(any())).thenAnswer((_) {}); bloc = _MockGameBloc(); }); @@ -73,10 +73,10 @@ void main() { initialState: initialState, ); final behavior = BonusNoiseBehavior(); - await game.pump(behavior, player: player, bloc: bloc); + await game.pump(behavior, audioPlayer: audioPlayer, bloc: bloc); }, verify: (_, __) async { - verify(() => player.play(PinballAudio.google)).called(1); + verify(() => audioPlayer.play(PinballAudio.google)).called(1); }, ); @@ -98,10 +98,10 @@ void main() { initialState: initialState, ); final behavior = BonusNoiseBehavior(); - await game.pump(behavior, player: player, bloc: bloc); + await game.pump(behavior, audioPlayer: audioPlayer, bloc: bloc); }, verify: (_, __) async { - verify(() => player.play(PinballAudio.sparky)).called(1); + verify(() => audioPlayer.play(PinballAudio.sparky)).called(1); }, ); @@ -123,11 +123,10 @@ void main() { initialState: initialState, ); final behavior = BonusNoiseBehavior(); - await game.pump(behavior, player: player, bloc: bloc); + await game.pump(behavior, audioPlayer: audioPlayer, bloc: bloc); }, verify: (_, __) async { - // TODO(erickzanardo): Change when the sound is implemented - verifyNever(() => player.play(any())); + verify(() => audioPlayer.play(PinballAudio.dino)).called(1); }, ); @@ -149,11 +148,10 @@ void main() { initialState: initialState, ); final behavior = BonusNoiseBehavior(); - await game.pump(behavior, player: player, bloc: bloc); + await game.pump(behavior, audioPlayer: audioPlayer, bloc: bloc); }, verify: (_, __) async { - // TODO(erickzanardo): Change when the sound is implemented - verifyNever(() => player.play(any())); + verify(() => audioPlayer.play(PinballAudio.android)).called(1); }, ); @@ -175,11 +173,10 @@ void main() { initialState: initialState, ); final behavior = BonusNoiseBehavior(); - await game.pump(behavior, player: player, bloc: bloc); + await game.pump(behavior, audioPlayer: audioPlayer, bloc: bloc); }, verify: (_, __) async { - // TODO(erickzanardo): Change when the sound is implemented - verifyNever(() => player.play(any())); + verify(() => audioPlayer.play(PinballAudio.dash)).called(1); }, ); }); diff --git a/test/game/behaviors/bumper_noise_behavior_test.dart b/test/game/behaviors/bumper_noise_behavior_test.dart index d8075726..cf6c7900 100644 --- a/test/game/behaviors/bumper_noise_behavior_test.dart +++ b/test/game/behaviors/bumper_noise_behavior_test.dart @@ -9,13 +9,14 @@ import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_flame/pinball_flame.dart'; class _TestGame extends Forge2DGame { - Future pump(_TestBodyComponent child, {required PinballPlayer player}) { + Future pump( + _TestBodyComponent child, { + required PinballAudioPlayer audioPlayer, + }) { return ensureAdd( - FlameProvider.value( - player, - children: [ - child, - ], + FlameProvider.value( + audioPlayer, + children: [child], ), ); } @@ -26,33 +27,33 @@ class _TestBodyComponent extends BodyComponent { Body createBody() => world.createBody(BodyDef()); } -class _MockPinballPlayer extends Mock implements PinballPlayer {} +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} class _MockContact extends Mock implements Contact {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('BumperNoiseBehavior', () {}); - - late PinballPlayer player; - final flameTester = FlameTester(_TestGame.new); - - setUp(() { - player = _MockPinballPlayer(); + group('BumperNoiseBehavior', () { + late PinballAudioPlayer audioPlayer; + final flameTester = FlameTester(_TestGame.new); + + setUp(() { + audioPlayer = _MockPinballAudioPlayer(); + }); + + flameTester.testGameWidget( + 'plays bumper sound', + setUp: (game, _) async { + final behavior = BumperNoiseBehavior(); + final parent = _TestBodyComponent(); + await game.pump(parent, audioPlayer: audioPlayer); + await parent.ensureAdd(behavior); + behavior.beginContact(Object(), _MockContact()); + }, + verify: (_, __) async { + verify(() => audioPlayer.play(PinballAudio.bumper)).called(1); + }, + ); }); - - flameTester.testGameWidget( - 'plays bumper sound', - setUp: (game, _) async { - final behavior = BumperNoiseBehavior(); - final parent = _TestBodyComponent(); - await game.pump(parent, player: player); - await parent.ensureAdd(behavior); - behavior.beginContact(Object(), _MockContact()); - }, - verify: (_, __) async { - verify(() => player.play(PinballAudio.bumper)).called(1); - }, - ); } diff --git a/test/game/behaviors/character_selection_behavior_test.dart b/test/game/behaviors/character_selection_behavior_test.dart new file mode 100644 index 00000000..67a8b0a5 --- /dev/null +++ b/test/game/behaviors/character_selection_behavior_test.dart @@ -0,0 +1,131 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/select_character/select_character.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:pinball_theme/pinball_theme.dart' as theme; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + theme.Assets.images.dash.ball.keyName, + theme.Assets.images.dino.ball.keyName, + theme.Assets.images.dash.background.keyName, + theme.Assets.images.dino.background.keyName, + ]); + } + + Future pump( + List children, { + CharacterThemeCubit? characterThemeBloc, + }) async { + await ensureAdd( + FlameBlocProvider.value( + value: characterThemeBloc ?? CharacterThemeCubit(), + children: children, + ), + ); + } +} + +class _MockBallCubit extends Mock implements BallCubit {} + +class _MockArcadeBackgroundCubit extends Mock implements ArcadeBackgroundCubit { +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group( + 'CharacterSelectionBehavior', + () { + final flameTester = FlameTester(_TestGame.new); + + test('can be instantiated', () { + expect( + CharacterSelectionBehavior(), + isA(), + ); + }); + + flameTester.test( + 'loads', + (game) async { + final behavior = CharacterSelectionBehavior(); + await game.pump([behavior]); + expect(game.descendants(), contains(behavior)); + }, + ); + + flameTester.test( + 'onNewState calls onCharacterSelected on the arcade background bloc', + (game) async { + final arcadeBackgroundBloc = _MockArcadeBackgroundCubit(); + whenListen( + arcadeBackgroundBloc, + const Stream.empty(), + initialState: const ArcadeBackgroundState.initial(), + ); + final arcadeBackground = + ArcadeBackground.test(bloc: arcadeBackgroundBloc); + final behavior = CharacterSelectionBehavior(); + await game.pump([ + arcadeBackground, + behavior, + ZCanvasComponent(), + Plunger.test(compressionDistance: 10), + Ball.test(), + ]); + + const dinoThemeState = CharacterThemeState(theme.DinoTheme()); + behavior.onNewState(dinoThemeState); + await game.ready(); + + verify( + () => arcadeBackgroundBloc + .onCharacterSelected(dinoThemeState.characterTheme), + ).called(1); + }, + ); + + flameTester.test( + 'onNewState calls onCharacterSelected on the ball bloc', + (game) async { + final ballBloc = _MockBallCubit(); + whenListen( + ballBloc, + const Stream.empty(), + initialState: const BallState.initial(), + ); + final ball = Ball.test(bloc: ballBloc); + final behavior = CharacterSelectionBehavior(); + await game.pump([ + ball, + behavior, + ZCanvasComponent(), + Plunger.test(compressionDistance: 10), + ArcadeBackground.test(), + ]); + + const dinoThemeState = CharacterThemeState(theme.DinoTheme()); + behavior.onNewState(dinoThemeState); + await game.ready(); + + verify( + () => ballBloc.onCharacterSelected(dinoThemeState.characterTheme), + ).called(1); + }, + ); + }, + ); +} diff --git a/test/game/behaviors/cow_bumper_noise_behavior_test.dart b/test/game/behaviors/cow_bumper_noise_behavior_test.dart new file mode 100644 index 00000000..27a62e0b --- /dev/null +++ b/test/game/behaviors/cow_bumper_noise_behavior_test.dart @@ -0,0 +1,58 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class _TestGame extends Forge2DGame { + Future pump( + _TestBodyComponent child, { + required PinballAudioPlayer audioPlayer, + }) { + return ensureAdd( + FlameProvider.value( + audioPlayer, + children: [child], + ), + ); + } +} + +class _TestBodyComponent extends BodyComponent { + @override + Body createBody() => world.createBody(BodyDef()); +} + +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} + +class _MockContact extends Mock implements Contact {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('CowBumperNoiseBehavior', () { + late PinballAudioPlayer audioPlayer; + final flameTester = FlameTester(_TestGame.new); + + setUp(() { + audioPlayer = _MockPinballAudioPlayer(); + }); + flameTester.testGameWidget( + 'plays cow moo sound on contact', + setUp: (game, _) async { + final behavior = CowBumperNoiseBehavior(); + final parent = _TestBodyComponent(); + await game.pump(parent, audioPlayer: audioPlayer); + await parent.ensureAdd(behavior); + behavior.beginContact(Object(), _MockContact()); + }, + verify: (_, __) async { + verify(() => audioPlayer.play(PinballAudio.cowMoo)).called(1); + }, + ); + }); +} diff --git a/test/game/behaviors/kicker_noise_behavior_test.dart b/test/game/behaviors/kicker_noise_behavior_test.dart new file mode 100644 index 00000000..4db18ab4 --- /dev/null +++ b/test/game/behaviors/kicker_noise_behavior_test.dart @@ -0,0 +1,59 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class _TestGame extends Forge2DGame { + Future pump( + _TestBodyComponent child, { + required PinballAudioPlayer audioPlayer, + }) { + return ensureAdd( + FlameProvider.value( + audioPlayer, + children: [child], + ), + ); + } +} + +class _TestBodyComponent extends BodyComponent { + @override + Body createBody() => world.createBody(BodyDef()); +} + +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} + +class _MockContact extends Mock implements Contact {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('KickerNoiseBehavior', () { + late PinballAudioPlayer audioPlayer; + final flameTester = FlameTester(_TestGame.new); + + setUp(() { + audioPlayer = _MockPinballAudioPlayer(); + }); + + flameTester.testGameWidget( + 'plays kicker sound', + setUp: (game, _) async { + final behavior = KickerNoiseBehavior(); + final parent = _TestBodyComponent(); + await game.pump(parent, audioPlayer: audioPlayer); + await parent.ensureAdd(behavior); + behavior.beginContact(Object(), _MockContact()); + }, + verify: (_, __) async { + verify(() => audioPlayer.play(PinballAudio.kicker)).called(1); + }, + ); + }); +} diff --git a/test/game/bloc/game_bloc_test.dart b/test/game/bloc/game_bloc_test.dart index 3e5abb74..2108b950 100644 --- a/test/game/bloc/game_bloc_test.dart +++ b/test/game/bloc/game_bloc_test.dart @@ -91,6 +91,28 @@ void main() { ], ); + blocTest( + "multiplier doesn't increase score above the max score", + build: GameBloc.new, + seed: () => const GameState( + totalScore: 9999999998, + roundScore: 1, + multiplier: 2, + rounds: 1, + bonusHistory: [], + status: GameStatus.playing, + ), + act: (bloc) => bloc.add(const RoundLost()), + expect: () => [ + isA() + ..having( + (state) => state.totalScore, + 'totalScore', + 9999999999, + ) + ], + ); + blocTest( 'resets multiplier when round is lost', build: GameBloc.new, @@ -167,6 +189,23 @@ void main() { ), ], ); + + blocTest( + "doesn't increase score above the max score", + build: GameBloc.new, + seed: () => const GameState( + totalScore: 9999999998, + roundScore: 0, + multiplier: 1, + rounds: 1, + bonusHistory: [], + status: GameStatus.playing, + ), + act: (bloc) => bloc.add(const Scored(points: 2)), + expect: () => [ + isA()..having((state) => state.roundScore, 'roundScore', 1) + ], + ); }); group('MultiplierIncreased', () { @@ -279,21 +318,5 @@ void main() { ); }, ); - - group('SparkyTurboChargeActivated', () { - blocTest( - 'adds game bonus', - build: GameBloc.new, - act: (bloc) => bloc..add(const SparkyTurboChargeActivated()), - expect: () => [ - isA() - ..having( - (state) => state.bonusHistory, - 'bonusHistory', - [GameBonus.sparkyTurboCharge], - ), - ], - ); - }); }); } diff --git a/test/game/bloc/game_event_test.dart b/test/game/bloc/game_event_test.dart index c4de5792..68e6a2e7 100644 --- a/test/game/bloc/game_event_test.dart +++ b/test/game/bloc/game_event_test.dart @@ -97,17 +97,4 @@ void main() { ); }); }); - - group('SparkyTurboChargeActivated', () { - test('can be instantiated', () { - expect(const SparkyTurboChargeActivated(), isNotNull); - }); - - test('supports value equality', () { - expect( - SparkyTurboChargeActivated(), - equals(SparkyTurboChargeActivated()), - ); - }); - }); } diff --git a/test/game/components/android_acres/android_acres_test.dart b/test/game/components/android_acres/android_acres_test.dart index e88d1608..5c750818 100644 --- a/test/game/components/android_acres/android_acres_test.dart +++ b/test/game/components/android_acres/android_acres_test.dart @@ -4,7 +4,7 @@ import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball/game/behaviors/bumper_noise_behavior.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -124,18 +124,50 @@ void main() { for (final bumper in bumpers) { expect( bumper.firstChild(), - isNotNull, + isA(), ); } }, ); + + flameTester.test( + 'one AndroidBumper with CowBumperNoiseBehavior', + (game) async { + await game.pump(AndroidAcres()); + final bumpers = game.descendants().whereType(); + + expect( + bumpers.singleWhere( + (bumper) => bumper.firstChild() != null, + ), + isA(), + ); + }, + ); + }); + + flameTester.test('adds a FlameBlocProvider', (game) async { + final androidAcres = AndroidAcres(); + await game.pump(androidAcres); + expect( + androidAcres.children + .whereType< + FlameBlocProvider>() + .single, + isNotNull, + ); }); flameTester.test('adds an AndroidSpaceshipBonusBehavior', (game) async { final androidAcres = AndroidAcres(); await game.pump(androidAcres); + final provider = androidAcres.children + .whereType< + FlameBlocProvider>() + .single; expect( - androidAcres.children.whereType().single, + provider.children.whereType().single, isNotNull, ); }); diff --git a/test/game/components/android_acres/behaviors/android_spaceship_bonus_behavior_test.dart b/test/game/components/android_acres/behaviors/android_spaceship_bonus_behavior_test.dart index e6b03c5f..e8ef68f8 100644 --- a/test/game/components/android_acres/behaviors/android_spaceship_bonus_behavior_test.dart +++ b/test/game/components/android_acres/behaviors/android_spaceship_bonus_behavior_test.dart @@ -1,5 +1,8 @@ // ignore_for_file: cascade_invocations +import 'dart:async'; + +import 'package:bloc_test/bloc_test.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; @@ -41,13 +44,21 @@ class _TestGame extends Forge2DGame { Future pump( AndroidAcres child, { required GameBloc gameBloc, + required AndroidSpaceshipCubit androidSpaceshipCubit, }) async { // Not needed once https://github.com/flame-engine/flame/issues/1607 // is fixed await onLoad(); await ensureAdd( - FlameBlocProvider.value( - value: gameBloc, + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value( + value: gameBloc, + ), + FlameBlocProvider.value( + value: androidSpaceshipCubit, + ), + ], children: [child], ), ); @@ -56,6 +67,9 @@ class _TestGame extends Forge2DGame { class _MockGameBloc extends Mock implements GameBloc {} +class _MockAndroidSpaceshipCubit extends Mock implements AndroidSpaceshipCubit { +} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -70,20 +84,30 @@ void main() { flameTester.testGameWidget( 'adds GameBonus.androidSpaceship to the game ' - 'when android spacehship has a bonus', + 'when android spaceship has a bonus', setUp: (game, tester) async { final behavior = AndroidSpaceshipBonusBehavior(); final parent = AndroidAcres.test(); final androidSpaceship = AndroidSpaceship(position: Vector2.zero()); + final androidSpaceshipCubit = _MockAndroidSpaceshipCubit(); + final streamController = StreamController(); + + whenListen( + androidSpaceshipCubit, + streamController.stream, + initialState: AndroidSpaceshipState.withoutBonus, + ); await parent.add(androidSpaceship); await game.pump( parent, + androidSpaceshipCubit: androidSpaceshipCubit, gameBloc: gameBloc, ); await parent.ensureAdd(behavior); - androidSpaceship.bloc.onBallEntered(); + streamController.add(AndroidSpaceshipState.withBonus); + await tester.pump(); verify( diff --git a/test/game/components/backbox/backbox_test.dart b/test/game/components/backbox/backbox_test.dart index d61bd83a..b99b86ab 100644 --- a/test/game/components/backbox/backbox_test.dart +++ b/test/game/components/backbox/backbox_test.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:bloc_test/bloc_test.dart'; +import 'package:flame/game.dart'; import 'package:flame/input.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; @@ -19,8 +20,13 @@ import 'package:pinball/l10n/l10n.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; +import 'package:pinball_ui/pinball_ui.dart'; +import 'package:platform_helper/platform_helper.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:share_repository/share_repository.dart'; -class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents { +class _TestGame extends Forge2DGame + with HasKeyboardHandlerComponents, HasTappables { final character = theme.DashTheme(); @override @@ -33,6 +39,9 @@ class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents { character.leaderboardIcon.keyName, Assets.images.backbox.marquee.keyName, Assets.images.backbox.displayDivider.keyName, + Assets.images.backbox.button.facebook.keyName, + Assets.images.backbox.button.twitter.keyName, + Assets.images.backbox.displayTitleDecoration.keyName, ]); } @@ -64,11 +73,21 @@ RawKeyUpEvent _mockKeyUp(LogicalKeyboardKey key) { return event; } +class _MockPlatformHelper extends Mock implements PlatformHelper {} + class _MockBackboxBloc extends Mock implements BackboxBloc {} class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { } +class _MockShareRepository extends Mock implements ShareRepository {} + +class _MockTapDownInfo extends Mock implements TapDownInfo {} + +class _MockUrlLauncher extends Mock + with MockPlatformInterfaceMixin + implements UrlLauncherPlatform {} + class _MockAppLocalizations extends Mock implements AppLocalizations { @override String get score => ''; @@ -96,6 +115,48 @@ class _MockAppLocalizations extends Mock implements AppLocalizations { @override String get loading => ''; + + @override + String get letEveryone => ''; + + @override + String get bySharingYourScore => ''; + + @override + String get socialMediaAccount => ''; + + @override + String get shareYourScore => ''; + + @override + String get andChallengeYourFriends => ''; + + @override + String get share => ''; + + @override + String get gotoIO => ''; + + @override + String get learnMore => ''; + + @override + String get firebaseOr => ''; + + @override + String get openSourceCode => ''; + + @override + String get initialsErrorTitle => ''; + + @override + String get initialsErrorMessage => ''; + + @override + String get leaderboardErrorMessage => ''; + + @override + String iGotScoreAtPinball(int _) => ''; } void main() { @@ -104,36 +165,34 @@ void main() { final flameTester = FlameTester(_TestGame.new); late BackboxBloc bloc; + late PlatformHelper platformHelper; + late UrlLauncherPlatform urlLauncher; setUp(() { bloc = _MockBackboxBloc(); + platformHelper = _MockPlatformHelper(); whenListen( bloc, Stream.empty(), initialState: LoadingState(), ); + when(() => platformHelper.isMobile).thenReturn(false); }); group('Backbox', () { flameTester.test( 'loads correctly', (game) async { - final backbox = Backbox.test(bloc: bloc); + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, + ); await game.pump(backbox); expect(game.descendants(), contains(backbox)); }, ); - flameTester.test( - 'adds LeaderboardRequested when loaded', - (game) async { - final backbox = Backbox.test(bloc: bloc); - await game.pump(backbox); - - verify(() => bloc.add(LeaderboardRequested())).called(1); - }, - ); - flameTester.testGameWidget( 'renders correctly', setUp: (game, tester) async { @@ -142,7 +201,11 @@ void main() { ..followVector2(Vector2(0, -130)) ..zoom = 6; await game.pump( - Backbox.test(bloc: bloc), + Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, + ), ); await tester.pump(); }, @@ -160,7 +223,10 @@ void main() { final backbox = Backbox.test( bloc: BackboxBloc( leaderboardRepository: _MockLeaderboardRepository(), + initialEntries: [LeaderboardEntryData.empty], ), + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, ); await game.pump(backbox); backbox.requestInitials( @@ -189,7 +255,11 @@ void main() { Stream.empty(), initialState: state, ); - final backbox = Backbox.test(bloc: bloc); + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, + ); await game.pump(backbox); game.onKeyEvent(_mockKeyUp(LogicalKeyboardKey.enter), {}); @@ -206,35 +276,152 @@ void main() { ); flameTester.test( - 'adds InitialsSubmissionSuccessDisplay on InitialsSuccessState', + 'adds GameOverInfoDisplay when InitialsSuccessState', (game) async { + final state = InitialsSuccessState(score: 100); + whenListen( + bloc, + const Stream.empty(), + initialState: state, + ); + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, + ); + await game.pump(backbox); + + expect( + game.descendants().whereType().length, + equals(1), + ); + }, + ); + + flameTester.test( + 'adds the mobile controls overlay ' + 'when platform is mobile at InitialsFormState', + (game) async { + final bloc = _MockBackboxBloc(); + final platformHelper = _MockPlatformHelper(); + final state = InitialsFormState( + score: 10, + character: game.character, + ); whenListen( bloc, Stream.empty(), - initialState: InitialsSuccessState(), + initialState: state, + ); + when(() => platformHelper.isMobile).thenReturn(true); + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, ); - final backbox = Backbox.test(bloc: bloc); await game.pump(backbox); expect( - game - .descendants() - .whereType() - .length, + game.overlays.value, + contains(PinballGame.mobileControlsOverlay), + ); + }, + ); + + flameTester.test( + 'remove the mobile controls overlay ' + 'when InitialsSuccessState', + (game) async { + final bloc = _MockBackboxBloc(); + final platformHelper = _MockPlatformHelper(); + final state = InitialsSuccessState(score: 10); + whenListen( + bloc, + Stream.empty(), + initialState: state, + ); + when(() => platformHelper.isMobile).thenReturn(true); + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, + ); + await game.pump(backbox); + + expect( + game.overlays.value, + isNot(contains(PinballGame.mobileControlsOverlay)), + ); + }, + ); + + flameTester.test( + 'adds InitialsSubmissionSuccessDisplay on InitialsSuccessState', + (game) async { + final state = InitialsSuccessState(score: 100); + whenListen( + bloc, + const Stream.empty(), + initialState: state, + ); + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, + ); + await game.pump(backbox); + + expect( + game.descendants().whereType().length, equals(1), ); }, ); + flameTester.test( + 'adds ShareScoreRequested event when sharing', + (game) async { + final state = InitialsSuccessState(score: 100); + whenListen( + bloc, + Stream.value(state), + initialState: state, + ); + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, + ); + await game.pump(backbox); + + final shareLink = + game.descendants().whereType().first; + shareLink.onTapDown(_MockTapDownInfo()); + + verify( + () => bloc.add( + ShareScoreRequested(score: state.score), + ), + ).called(1); + }, + ); + flameTester.test( 'adds InitialsSubmissionFailureDisplay on InitialsFailureState', (game) async { whenListen( bloc, Stream.empty(), - initialState: InitialsFailureState(), + initialState: InitialsFailureState( + score: 0, + character: theme.DashTheme(), + ), + ); + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, ); - final backbox = Backbox.test(bloc: bloc); await game.pump(backbox); expect( @@ -247,6 +434,145 @@ void main() { }, ); + group('ShareDisplay', () { + setUp(() async { + urlLauncher = _MockUrlLauncher(); + UrlLauncherPlatform.instance = urlLauncher; + }); + + flameTester.test( + 'adds ShareDisplay on ShareState', + (game) async { + final state = ShareState(score: 100); + whenListen( + bloc, + const Stream.empty(), + initialState: state, + ); + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, + ); + await game.pump(backbox); + + expect( + game.descendants().whereType().length, + equals(1), + ); + }, + ); + + flameTester.test( + 'opens Facebook link when sharing with Facebook', + (game) async { + when(() => urlLauncher.canLaunch(any())) + .thenAnswer((_) async => true); + when( + () => urlLauncher.launch( + any(), + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ).thenAnswer((_) async => true); + + final state = ShareState(score: 100); + whenListen( + bloc, + const Stream.empty(), + initialState: state, + ); + + final shareRepository = _MockShareRepository(); + const fakeUrl = 'http://fakeUrl'; + when( + () => shareRepository.shareText( + value: any(named: 'value'), + platform: SharePlatform.facebook, + ), + ).thenReturn(fakeUrl); + + final backbox = Backbox.test( + bloc: bloc, + shareRepository: shareRepository, + platformHelper: platformHelper, + ); + await game.pump(backbox); + + final facebookButton = + game.descendants().whereType().first; + facebookButton.onTapDown(_MockTapDownInfo()); + + await game.ready(); + + verify( + () => shareRepository.shareText( + value: any(named: 'value'), + platform: SharePlatform.facebook, + ), + ).called(1); + }, + ); + + flameTester.test( + 'opens Twitter link when sharing with Twitter', + (game) async { + final state = ShareState(score: 100); + whenListen( + bloc, + Stream.value(state), + initialState: state, + ); + + final shareRepository = _MockShareRepository(); + const fakeUrl = 'http://fakeUrl'; + when( + () => shareRepository.shareText( + value: any(named: 'value'), + platform: SharePlatform.twitter, + ), + ).thenReturn(fakeUrl); + when(() => urlLauncher.canLaunch(any())) + .thenAnswer((_) async => true); + when( + () => urlLauncher.launch( + any(), + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ).thenAnswer((_) async => true); + + final backbox = Backbox.test( + bloc: bloc, + shareRepository: shareRepository, + platformHelper: platformHelper, + ); + await game.pump(backbox); + + final facebookButton = + game.descendants().whereType().first; + facebookButton.onTapDown(_MockTapDownInfo()); + + await game.ready(); + + verify( + () => shareRepository.shareText( + value: any(named: 'value'), + platform: SharePlatform.twitter, + ), + ).called(1); + }, + ); + }); + flameTester.test( 'adds LeaderboardDisplay on LeaderboardSuccessState', (game) async { @@ -256,7 +582,11 @@ void main() { initialState: LeaderboardSuccessState(entries: const []), ); - final backbox = Backbox.test(bloc: bloc); + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, + ); await game.pump(backbox); expect( @@ -266,6 +596,29 @@ void main() { }, ); + flameTester.test( + 'adds LeaderboardFailureDisplay on LeaderboardFailureState', + (game) async { + whenListen( + bloc, + Stream.empty(), + initialState: LeaderboardFailureState(), + ); + + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, + ); + await game.pump(backbox); + + expect( + game.descendants().whereType().length, + equals(1), + ); + }, + ); + flameTester.test( 'closes the subscription when it is removed', (game) async { @@ -276,13 +629,22 @@ void main() { initialState: LoadingState(), ); - final backbox = Backbox.test(bloc: bloc); + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, + ); await game.pump(backbox); backbox.removeFromParent(); await game.ready(); - streamController.add(InitialsFailureState()); + streamController.add( + InitialsFailureState( + score: 10, + character: theme.DashTheme(), + ), + ); await game.ready(); expect( @@ -294,5 +656,37 @@ void main() { ); }, ); + + flameTester.test( + 'adds PlayerInitialsSubmitted when the timer is finished', + (game) async { + final initialState = InitialsFailureState( + score: 10, + character: theme.DashTheme(), + ); + whenListen( + bloc, + Stream.fromIterable([]), + initialState: initialState, + ); + + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + platformHelper: platformHelper, + ); + await game.pump(backbox); + game.update(4); + + verify( + () => bloc.add( + PlayerInitialsRequested( + score: 10, + character: theme.DashTheme(), + ), + ), + ).called(1); + }, + ); }); } diff --git a/test/game/components/backbox/bloc/backbox_bloc_test.dart b/test/game/components/backbox/bloc/backbox_bloc_test.dart index 3958adb5..050307dc 100644 --- a/test/game/components/backbox/bloc/backbox_bloc_test.dart +++ b/test/game/components/backbox/bloc/backbox_bloc_test.dart @@ -12,14 +12,37 @@ class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { void main() { late LeaderboardRepository leaderboardRepository; + const emptyEntries = []; + const filledEntries = [LeaderboardEntryData.empty]; group('BackboxBloc', () { + test('inits state with LeaderboardSuccessState when has entries', () { + leaderboardRepository = _MockLeaderboardRepository(); + final bloc = BackboxBloc( + leaderboardRepository: leaderboardRepository, + initialEntries: filledEntries, + ); + expect(bloc.state, isA()); + }); + + test('inits state with LeaderboardFailureState when has no entries', () { + leaderboardRepository = _MockLeaderboardRepository(); + final bloc = BackboxBloc( + leaderboardRepository: leaderboardRepository, + initialEntries: null, + ); + expect(bloc.state, isA()); + }); + blocTest( 'adds InitialsFormState on PlayerInitialsRequested', setUp: () { leaderboardRepository = _MockLeaderboardRepository(); }, - build: () => BackboxBloc(leaderboardRepository: leaderboardRepository), + build: () => BackboxBloc( + leaderboardRepository: leaderboardRepository, + initialEntries: emptyEntries, + ), act: (bloc) => bloc.add( PlayerInitialsRequested( score: 100, @@ -46,7 +69,10 @@ void main() { ), ).thenAnswer((_) async {}); }, - build: () => BackboxBloc(leaderboardRepository: leaderboardRepository), + build: () => BackboxBloc( + leaderboardRepository: leaderboardRepository, + initialEntries: emptyEntries, + ), act: (bloc) => bloc.add( PlayerInitialsSubmitted( score: 10, @@ -56,7 +82,7 @@ void main() { ), expect: () => [ LoadingState(), - InitialsSuccessState(), + InitialsSuccessState(score: 10), ], ); @@ -74,7 +100,10 @@ void main() { ), ).thenThrow(Exception('Error')); }, - build: () => BackboxBloc(leaderboardRepository: leaderboardRepository), + build: () => BackboxBloc( + leaderboardRepository: leaderboardRepository, + initialEntries: emptyEntries, + ), act: (bloc) => bloc.add( PlayerInitialsSubmitted( score: 10, @@ -84,7 +113,45 @@ void main() { ), expect: () => [ LoadingState(), - InitialsFailureState(), + InitialsFailureState(score: 10, character: DashTheme()), + ], + ); + }); + + group('ShareScoreRequested', () { + blocTest( + 'emits ShareState', + setUp: () { + leaderboardRepository = _MockLeaderboardRepository(); + }, + build: () => BackboxBloc( + leaderboardRepository: leaderboardRepository, + initialEntries: emptyEntries, + ), + act: (bloc) => bloc.add( + ShareScoreRequested(score: 100), + ), + expect: () => [ + ShareState(score: 100), + ], + ); + }); + + group('ShareScoreRequested', () { + blocTest( + 'emits ShareState', + setUp: () { + leaderboardRepository = _MockLeaderboardRepository(); + }, + build: () => BackboxBloc( + leaderboardRepository: leaderboardRepository, + initialEntries: emptyEntries, + ), + act: (bloc) => bloc.add( + ShareScoreRequested(score: 100), + ), + expect: () => [ + ShareState(score: 100), ], ); }); @@ -100,7 +167,10 @@ void main() { (_) async => [LeaderboardEntryData.empty], ); }, - build: () => BackboxBloc(leaderboardRepository: leaderboardRepository), + build: () => BackboxBloc( + leaderboardRepository: leaderboardRepository, + initialEntries: emptyEntries, + ), act: (bloc) => bloc.add(LeaderboardRequested()), expect: () => [ LoadingState(), @@ -116,7 +186,10 @@ void main() { () => leaderboardRepository.fetchTop10Leaderboard(), ).thenThrow(Exception('Error')); }, - build: () => BackboxBloc(leaderboardRepository: leaderboardRepository), + build: () => BackboxBloc( + leaderboardRepository: leaderboardRepository, + initialEntries: emptyEntries, + ), act: (bloc) => bloc.add(LeaderboardRequested()), expect: () => [ LoadingState(), diff --git a/test/game/components/backbox/bloc/backbox_event_test.dart b/test/game/components/backbox/bloc/backbox_event_test.dart index 80f7fbb1..35f74ab5 100644 --- a/test/game/components/backbox/bloc/backbox_event_test.dart +++ b/test/game/components/backbox/bloc/backbox_event_test.dart @@ -123,6 +123,33 @@ void main() { }); }); + group('ScoreShareRequested', () { + test('can be instantiated', () { + expect( + ShareScoreRequested(score: 0), + isNotNull, + ); + }); + + test('supports value comparison', () { + expect( + ShareScoreRequested(score: 0), + equals( + ShareScoreRequested(score: 0), + ), + ); + + expect( + ShareScoreRequested(score: 0), + isNot( + equals( + ShareScoreRequested(score: 1), + ), + ), + ); + }); + }); + group('LeaderboardRequested', () { test('can be instantiated', () { expect(LeaderboardRequested(), isNotNull); diff --git a/test/game/components/backbox/bloc/backbox_state_test.dart b/test/game/components/backbox/bloc/backbox_state_test.dart index dd262408..ba6d1c08 100644 --- a/test/game/components/backbox/bloc/backbox_state_test.dart +++ b/test/game/components/backbox/bloc/backbox_state_test.dart @@ -115,22 +115,111 @@ void main() { group('InitialsSuccessState', () { test('can be instantiated', () { - expect(InitialsSuccessState(), isNotNull); + expect( + InitialsSuccessState(score: 0), + isNotNull, + ); }); test('supports value comparison', () { - expect(InitialsSuccessState(), equals(InitialsSuccessState())); + expect( + InitialsSuccessState(score: 0), + equals( + InitialsSuccessState(score: 0), + ), + ); }); group('InitialsFailureState', () { test('can be instantiated', () { - expect(InitialsFailureState(), isNotNull); + expect( + InitialsFailureState( + score: 10, + character: AndroidTheme(), + ), + isNotNull, + ); + }); + + test('supports value comparison', () { + expect( + InitialsFailureState( + score: 10, + character: AndroidTheme(), + ), + equals( + InitialsFailureState( + score: 10, + character: AndroidTheme(), + ), + ), + ); + expect( + InitialsFailureState( + score: 10, + character: AndroidTheme(), + ), + isNot( + equals( + InitialsFailureState( + score: 12, + character: AndroidTheme(), + ), + ), + ), + ); + expect( + InitialsFailureState( + score: 10, + character: AndroidTheme(), + ), + isNot( + equals( + InitialsFailureState( + score: 10, + character: DashTheme(), + ), + ), + ), + ); + }); + }); + + group('ShareState', () { + test('can be instantiated', () { + expect( + ShareState(score: 0), + isNotNull, + ); }); test('supports value comparison', () { - expect(InitialsFailureState(), equals(InitialsFailureState())); + expect( + ShareState(score: 0), + equals( + ShareState(score: 0), + ), + ); }); }); }); + + group('ShareState', () { + test('can be instantiated', () { + expect( + ShareState(score: 0), + isNotNull, + ); + }); + + test('supports value comparison', () { + expect( + ShareState(score: 0), + equals( + ShareState(score: 0), + ), + ); + }); + }); }); } diff --git a/test/game/components/backbox/displays/game_over_info_display_test.dart b/test/game/components/backbox/displays/game_over_info_display_test.dart new file mode 100644 index 00000000..6b79d9fd --- /dev/null +++ b/test/game/components/backbox/displays/game_over_info_display_test.dart @@ -0,0 +1,195 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/game.dart'; +import 'package:flame/input.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/bloc/game_bloc.dart'; +import 'package:pinball/game/components/backbox/displays/game_over_info_display.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:pinball_ui/pinball_ui.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:share_repository/share_repository.dart'; + +class _TestGame extends Forge2DGame with HasTappables { + @override + Future onLoad() async { + await super.onLoad(); + images.prefix = ''; + await images.loadAll( + [ + Assets.images.backbox.displayTitleDecoration.keyName, + ], + ); + } + + Future pump(GameOverInfoDisplay component) { + return ensureAdd( + FlameBlocProvider.value( + value: GameBloc(), + children: [ + FlameProvider.value( + _MockAppLocalizations(), + children: [component], + ), + ], + ), + ); + } +} + +class _MockAppLocalizations extends Mock implements AppLocalizations { + @override + String get shareYourScore => ''; + + @override + String get andChallengeYourFriends => ''; + + @override + String get share => ''; + + @override + String get gotoIO => ''; + + @override + String get learnMore => ''; + + @override + String get firebaseOr => ''; + + @override + String get openSourceCode => ''; +} + +class _MockTapDownInfo extends Mock implements TapDownInfo {} + +class _MockUrlLauncher extends Mock + with MockPlatformInterfaceMixin + implements UrlLauncherPlatform {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final flameTester = FlameTester(_TestGame.new); + + late UrlLauncherPlatform urlLauncher; + + setUp(() async { + urlLauncher = _MockUrlLauncher(); + UrlLauncherPlatform.instance = urlLauncher; + }); + + group('InfoDisplay', () { + flameTester.test( + 'loads correctly', + (game) async { + final component = GameOverInfoDisplay(); + await game.pump(component); + expect(game.descendants(), contains(component)); + }, + ); + + flameTester.test( + 'calls onShare when Share link is tapped', + (game) async { + var tapped = false; + + final tapDownInfo = _MockTapDownInfo(); + final component = GameOverInfoDisplay( + onShare: () => tapped = true, + ); + await game.pump(component); + + final shareLink = + component.descendants().whereType().first; + + shareLink.onTapDown(tapDownInfo); + + expect(tapped, isTrue); + }, + ); + + flameTester.test( + 'open Google IO Event url when navigating', + (game) async { + when(() => urlLauncher.canLaunch(any())).thenAnswer((_) async => true); + when( + () => urlLauncher.launch( + any(), + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ).thenAnswer((_) async => true); + + final component = GameOverInfoDisplay(); + await game.pump(component); + + final googleLink = + component.descendants().whereType().first; + googleLink.onTapDown(_MockTapDownInfo()); + + await game.ready(); + + verify( + () => urlLauncher.launch( + ShareRepository.googleIOEvent, + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ); + }, + ); + + flameTester.test( + 'open OpenSource url when navigating', + (game) async { + when(() => urlLauncher.canLaunch(any())).thenAnswer((_) async => true); + when( + () => urlLauncher.launch( + any(), + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ).thenAnswer((_) async => true); + + final component = GameOverInfoDisplay(); + await game.pump(component); + + final openSourceLink = + component.descendants().whereType().first; + openSourceLink.onTapDown(_MockTapDownInfo()); + + await game.ready(); + + verify( + () => urlLauncher.launch( + ShareRepository.openSourceCode, + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ); + }, + ); + }); +} diff --git a/test/game/components/backbox/displays/initials_input_display_test.dart b/test/game/components/backbox/displays/initials_input_display_test.dart index 1b92aedd..fd61c2c2 100644 --- a/test/game/components/backbox/displays/initials_input_display_test.dart +++ b/test/game/components/backbox/displays/initials_input_display_test.dart @@ -129,7 +129,7 @@ void main() { }, ); - String? submitedInitials; + String? submittedInitials; flameTester.testGameWidget( 'submits the initials', setUp: (game, tester) async { @@ -138,7 +138,7 @@ void main() { score: 1000, characterIconPath: game.characterIconPath, onSubmit: (value) { - submitedInitials = value; + submittedInitials = value; }, ); await game.pump(component); @@ -147,7 +147,7 @@ void main() { await tester.pump(); }, verify: (game, tester) async { - expect(submitedInitials, equals('AAA')); + expect(submittedInitials, equals('AAA')); }, ); diff --git a/test/game/components/backbox/displays/initials_submission_failure_display_test.dart b/test/game/components/backbox/displays/initials_submission_failure_display_test.dart index b37b41e7..f0eb03c6 100644 --- a/test/game/components/backbox/displays/initials_submission_failure_display_test.dart +++ b/test/game/components/backbox/displays/initials_submission_failure_display_test.dart @@ -4,18 +4,74 @@ import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/components/backbox/displays/initials_submission_failure_display.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball_components/gen/assets.gen.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + await super.onLoad(); + images.prefix = ''; + await images.loadAll( + [ + Assets.images.errorBackground.keyName, + ], + ); + } + + Future pump(InitialsSubmissionFailureDisplay component) { + return ensureAdd( + FlameProvider.value( + _MockAppLocalizations(), + children: [component], + ), + ); + } +} + +class _MockAppLocalizations extends Mock implements AppLocalizations { + @override + String get initialsErrorTitle => 'Title'; + + @override + String get initialsErrorMessage => 'Message'; +} void main() { + TestWidgetsFlutterBinding.ensureInitialized(); group('InitialsSubmissionFailureDisplay', () { - final flameTester = FlameTester(Forge2DGame.new); + final flameTester = FlameTester(_TestGame.new); flameTester.test('renders correctly', (game) async { - await game.ensureAdd(InitialsSubmissionFailureDisplay()); + await game.pump( + InitialsSubmissionFailureDisplay( + onDismissed: () {}, + ), + ); - final component = game.firstChild(); - expect(component, isNotNull); - expect(component?.text, equals('Failure!')); + expect( + game + .descendants() + .where( + (component) => + component is TextComponent && component.text == 'Title', + ) + .length, + equals(1), + ); + expect( + game + .descendants() + .where( + (component) => + component is TextComponent && component.text == 'Message', + ) + .length, + equals(1), + ); }); }); } diff --git a/test/game/components/backbox/displays/leaderboard_failure_display_test.dart b/test/game/components/backbox/displays/leaderboard_failure_display_test.dart new file mode 100644 index 00000000..8ce4c839 --- /dev/null +++ b/test/game/components/backbox/displays/leaderboard_failure_display_test.dart @@ -0,0 +1,60 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/components/backbox/displays/displays.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball_components/gen/assets.gen.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + await super.onLoad(); + images.prefix = ''; + await images.loadAll( + [ + Assets.images.errorBackground.keyName, + ], + ); + } + + Future pump(LeaderboardFailureDisplay component) { + return ensureAdd( + FlameProvider.value( + _MockAppLocalizations(), + children: [component], + ), + ); + } +} + +class _MockAppLocalizations extends Mock implements AppLocalizations { + @override + String get leaderboardErrorMessage => 'Message'; +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + group('LeaderboardFailureDisplay', () { + final flameTester = FlameTester(_TestGame.new); + + flameTester.test('renders correctly', (game) async { + await game.pump(LeaderboardFailureDisplay()); + + expect( + game + .descendants() + .where( + (component) => + component is TextComponent && component.text == 'Message', + ) + .length, + equals(1), + ); + }); + }); +} diff --git a/test/game/components/backbox/displays/share_display_test.dart b/test/game/components/backbox/displays/share_display_test.dart new file mode 100644 index 00000000..1f882223 --- /dev/null +++ b/test/game/components/backbox/displays/share_display_test.dart @@ -0,0 +1,112 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/game.dart'; +import 'package:flame/input.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/bloc/game_bloc.dart'; +import 'package:pinball/game/components/backbox/displays/share_display.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class _TestGame extends Forge2DGame with HasTappables { + @override + Future onLoad() async { + await super.onLoad(); + images.prefix = ''; + await images.loadAll( + [ + Assets.images.backbox.button.facebook.keyName, + Assets.images.backbox.button.twitter.keyName, + ], + ); + } + + Future pump(ShareDisplay component) { + return ensureAdd( + FlameBlocProvider.value( + value: GameBloc(), + children: [ + FlameProvider.value( + _MockAppLocalizations(), + children: [component], + ), + ], + ), + ); + } +} + +class _MockAppLocalizations extends Mock implements AppLocalizations { + @override + String get letEveryone => ''; + + @override + String get bySharingYourScore => ''; + + @override + String get socialMediaAccount => ''; +} + +class _MockTapDownInfo extends Mock implements TapDownInfo {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final flameTester = FlameTester(_TestGame.new); + + group('ShareDisplay', () { + flameTester.test( + 'loads correctly', + (game) async { + final component = ShareDisplay(); + await game.pump(component); + expect(game.descendants(), contains(component)); + }, + ); + + flameTester.test( + 'calls onShare when Facebook button is tapped', + (game) async { + var tapped = false; + + final tapDownInfo = _MockTapDownInfo(); + final component = ShareDisplay( + onShare: (_) => tapped = true, + ); + await game.pump(component); + + final facebookButton = + component.descendants().whereType().first; + + facebookButton.onTapDown(tapDownInfo); + + expect(tapped, isTrue); + }, + ); + + flameTester.test( + 'calls onShare when Twitter button is tapped', + (game) async { + var tapped = false; + + final tapDownInfo = _MockTapDownInfo(); + final component = ShareDisplay( + onShare: (_) => tapped = true, + ); + await game.pump(component); + + final twitterButton = + component.descendants().whereType().first; + + twitterButton.onTapDown(tapDownInfo); + + expect(tapped, isTrue); + }, + ); + }); +} diff --git a/test/game/components/bottom_group_test.dart b/test/game/components/bottom_group_test.dart index fab8dfaf..832d96d6 100644 --- a/test/game/components/bottom_group_test.dart +++ b/test/game/components/bottom_group_test.dart @@ -95,9 +95,9 @@ void main() { ), ); - final basebottomGroups = + final baseBottomGroups = bottomGroup.descendants().whereType(); - expect(basebottomGroups.length, equals(2)); + expect(baseBottomGroups.length, equals(2)); }, ); diff --git a/test/game/components/controlled_flipper_test.dart b/test/game/components/controlled_flipper_test.dart deleted file mode 100644 index 00a69f9e..00000000 --- a/test/game/components/controlled_flipper_test.dart +++ /dev/null @@ -1,260 +0,0 @@ -import 'dart:collection'; - -import 'package:bloc_test/bloc_test.dart'; -import 'package:flame/input.dart'; -import 'package:flame_bloc/flame_bloc.dart'; -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:pinball/game/game.dart'; -import 'package:pinball_components/pinball_components.dart'; - -import '../../helpers/helpers.dart'; - -class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents { - @override - Future onLoad() async { - images.prefix = ''; - await images.loadAll([ - Assets.images.flipper.left.keyName, - Assets.images.flipper.right.keyName, - ]); - } - - Future pump(Flipper flipper, {required GameBloc gameBloc}) { - return ensureAdd( - FlameBlocProvider.value( - value: gameBloc, - children: [flipper], - ), - ); - } -} - -class _MockGameBloc extends Mock implements GameBloc {} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(_TestGame.new); - - group('FlipperController', () { - late GameBloc gameBloc; - - setUp(() { - gameBloc = _MockGameBloc(); - }); - - group('onKeyEvent', () { - final leftKeys = UnmodifiableListView([ - LogicalKeyboardKey.arrowLeft, - LogicalKeyboardKey.keyA, - ]); - final rightKeys = UnmodifiableListView([ - LogicalKeyboardKey.arrowRight, - LogicalKeyboardKey.keyD, - ]); - - group('and Flipper is left', () { - late Flipper flipper; - late FlipperController controller; - - setUp(() { - flipper = Flipper(side: BoardSide.left); - controller = FlipperController(flipper); - flipper.add(controller); - }); - - testRawKeyDownEvents(leftKeys, (event) { - flameTester.test( - 'moves upwards ' - 'when ${event.logicalKey.keyLabel} is pressed', - (game) async { - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial().copyWith( - status: GameStatus.playing, - ), - ); - - await game.ready(); - await game.pump(flipper, gameBloc: gameBloc); - controller.onKeyEvent(event, {}); - - expect(flipper.body.linearVelocity.y, isNegative); - expect(flipper.body.linearVelocity.x, isZero); - }, - ); - }); - - testRawKeyDownEvents(leftKeys, (event) { - flameTester.test( - 'does nothing when is game over', - (game) async { - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial().copyWith( - status: GameStatus.gameOver, - ), - ); - - await game.pump(flipper, gameBloc: gameBloc); - controller.onKeyEvent(event, {}); - - expect(flipper.body.linearVelocity.y, isZero); - expect(flipper.body.linearVelocity.x, isZero); - }, - ); - }); - - testRawKeyUpEvents(leftKeys, (event) { - flameTester.test( - 'moves downwards ' - 'when ${event.logicalKey.keyLabel} is released', - (game) async { - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial().copyWith( - status: GameStatus.playing, - ), - ); - - await game.ready(); - await game.pump(flipper, gameBloc: gameBloc); - controller.onKeyEvent(event, {}); - - expect(flipper.body.linearVelocity.y, isPositive); - expect(flipper.body.linearVelocity.x, isZero); - }, - ); - }); - - testRawKeyUpEvents(rightKeys, (event) { - flameTester.test( - 'does nothing ' - 'when ${event.logicalKey.keyLabel} is released', - (game) async { - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial(), - ); - - await game.ready(); - await game.pump(flipper, gameBloc: gameBloc); - controller.onKeyEvent(event, {}); - - expect(flipper.body.linearVelocity.y, isZero); - expect(flipper.body.linearVelocity.x, isZero); - }, - ); - }); - }); - - group('and Flipper is right', () { - late Flipper flipper; - late FlipperController controller; - - setUp(() { - flipper = Flipper(side: BoardSide.right); - controller = FlipperController(flipper); - flipper.add(controller); - }); - - testRawKeyDownEvents(rightKeys, (event) { - flameTester.test( - 'moves upwards ' - 'when ${event.logicalKey.keyLabel} is pressed', - (game) async { - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial().copyWith( - status: GameStatus.playing, - ), - ); - - await game.ready(); - await game.pump(flipper, gameBloc: gameBloc); - controller.onKeyEvent(event, {}); - - expect(flipper.body.linearVelocity.y, isNegative); - expect(flipper.body.linearVelocity.x, isZero); - }, - ); - }); - - testRawKeyUpEvents(rightKeys, (event) { - flameTester.test( - 'moves downwards ' - 'when ${event.logicalKey.keyLabel} is released', - (game) async { - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial().copyWith( - status: GameStatus.playing, - ), - ); - - await game.ready(); - await game.pump(flipper, gameBloc: gameBloc); - controller.onKeyEvent(event, {}); - - expect(flipper.body.linearVelocity.y, isPositive); - expect(flipper.body.linearVelocity.x, isZero); - }, - ); - }); - - testRawKeyDownEvents(rightKeys, (event) { - flameTester.test( - 'does nothing when is game over', - (game) async { - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial().copyWith( - status: GameStatus.gameOver, - ), - ); - - await game.pump(flipper, gameBloc: gameBloc); - controller.onKeyEvent(event, {}); - - expect(flipper.body.linearVelocity.y, isZero); - expect(flipper.body.linearVelocity.x, isZero); - }, - ); - }); - - testRawKeyUpEvents(leftKeys, (event) { - flameTester.test( - 'does nothing ' - 'when ${event.logicalKey.keyLabel} is released', - (game) async { - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial().copyWith( - status: GameStatus.playing, - ), - ); - - await game.ready(); - await game.pump(flipper, gameBloc: gameBloc); - controller.onKeyEvent(event, {}); - - expect(flipper.body.linearVelocity.y, isZero); - expect(flipper.body.linearVelocity.x, isZero); - }, - ); - }); - }); - }); - }); -} diff --git a/test/game/components/controlled_plunger_test.dart b/test/game/components/controlled_plunger_test.dart index 25b1f739..68bde767 100644 --- a/test/game/components/controlled_plunger_test.dart +++ b/test/game/components/controlled_plunger_test.dart @@ -28,15 +28,15 @@ class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents { Future pump( Plunger child, { GameBloc? gameBloc, - PinballPlayer? pinballPlayer, + PinballAudioPlayer? pinballAudioPlayer, }) { return ensureAdd( FlameBlocProvider.value( value: gameBloc ?? GameBloc() ..add(const GameStarted()), children: [ - FlameProvider.value( - pinballPlayer ?? _MockPinballPlayer(), + FlameProvider.value( + pinballAudioPlayer ?? _MockPinballAudioPlayer(), children: [child], ) ], @@ -47,7 +47,7 @@ class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents { class _MockGameBloc extends Mock implements GameBloc {} -class _MockPinballPlayer extends Mock implements PinballPlayer {} +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -158,17 +158,17 @@ void main() { }); group('PlungerNoiseBehavior', () { - late PinballPlayer player; + late PinballAudioPlayer audioPlayer; setUp(() { - player = _MockPinballPlayer(); + audioPlayer = _MockPinballAudioPlayer(); }); flameTester.test('plays the correct sound on load', (game) async { final parent = ControlledPlunger(compressionDistance: 10); - await game.pump(parent, pinballPlayer: player); + await game.pump(parent, pinballAudioPlayer: audioPlayer); await parent.ensureAdd(PlungerNoiseBehavior()); - verify(() => player.play(PinballAudio.launcher)).called(1); + verify(() => audioPlayer.play(PinballAudio.launcher)).called(1); }); test('is removed on the first update', () { diff --git a/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart b/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart index 3dcd870b..7fc1946b 100644 --- a/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart +++ b/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart @@ -5,6 +5,7 @@ import 'package:flame_forge2d/forge2d_game.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/components/flutter_forest/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -29,13 +30,8 @@ class _TestGame extends Forge2DGame { FlameBlocProvider.value( value: gameBloc, children: [ - FlameProvider.value( - const theme.DashTheme(), - children: [ - ZCanvasComponent( - children: [child], - ), - ], + ZCanvasComponent( + children: [child], ), ], ), @@ -57,8 +53,7 @@ void main() { final flameTester = FlameTester(_TestGame.new); - void _contactedBumper(DashNestBumper bumper) => - bumper.bloc.onBallContacted(); + void _contactedBumper(DashBumper bumper) => bumper.bloc.onBallContacted(); flameTester.testGameWidget( 'adds GameBonus.dashNest to the game ' @@ -68,9 +63,9 @@ void main() { final behavior = FlutterForestBonusBehavior(); final parent = FlutterForest.test(); final bumpers = [ - DashNestBumper.test(bloc: DashNestBumperCubit()), - DashNestBumper.test(bloc: DashNestBumperCubit()), - DashNestBumper.test(bloc: DashNestBumperCubit()), + DashBumper.test(bloc: DashBumperCubit()), + DashBumper.test(bloc: DashBumperCubit()), + DashBumper.test(bloc: DashBumperCubit()), ]; final animatronic = DashAnimatronic(); final signpost = Signpost.test(bloc: SignpostCubit()); @@ -78,7 +73,7 @@ void main() { await parent.ensureAddAll([...bumpers, animatronic, signpost]); await parent.ensureAdd(behavior); - expect(game.descendants().whereType(), equals(bumpers)); + expect(game.descendants().whereType(), equals(bumpers)); bumpers.forEach(_contactedBumper); await tester.pump(); bumpers.forEach(_contactedBumper); @@ -93,16 +88,16 @@ void main() { ); flameTester.testGameWidget( - 'adds a new Ball to the game ' + 'adds BonusBallSpawningBehavior to the game ' 'when bumpers are activated three times', setUp: (game, tester) async { await game.onLoad(); final behavior = FlutterForestBonusBehavior(); final parent = FlutterForest.test(); final bumpers = [ - DashNestBumper.test(bloc: DashNestBumperCubit()), - DashNestBumper.test(bloc: DashNestBumperCubit()), - DashNestBumper.test(bloc: DashNestBumperCubit()), + DashBumper.test(bloc: DashBumperCubit()), + DashBumper.test(bloc: DashBumperCubit()), + DashBumper.test(bloc: DashBumperCubit()), ]; final animatronic = DashAnimatronic(); final signpost = Signpost.test(bloc: SignpostCubit()); @@ -110,7 +105,7 @@ void main() { await parent.ensureAddAll([...bumpers, animatronic, signpost]); await parent.ensureAdd(behavior); - expect(game.descendants().whereType(), equals(bumpers)); + expect(game.descendants().whereType(), equals(bumpers)); bumpers.forEach(_contactedBumper); await tester.pump(); bumpers.forEach(_contactedBumper); @@ -120,7 +115,7 @@ void main() { await game.ready(); expect( - game.descendants().whereType().length, + game.descendants().whereType().length, equals(1), ); }, @@ -134,9 +129,9 @@ void main() { final behavior = FlutterForestBonusBehavior(); final parent = FlutterForest.test(); final bumpers = [ - DashNestBumper.test(bloc: DashNestBumperCubit()), - DashNestBumper.test(bloc: DashNestBumperCubit()), - DashNestBumper.test(bloc: DashNestBumperCubit()), + DashBumper.test(bloc: DashBumperCubit()), + DashBumper.test(bloc: DashBumperCubit()), + DashBumper.test(bloc: DashBumperCubit()), ]; final animatronic = DashAnimatronic(); final signpost = Signpost.test(bloc: SignpostCubit()); @@ -144,7 +139,7 @@ void main() { await parent.ensureAddAll([...bumpers, animatronic, signpost]); await parent.ensureAdd(behavior); - expect(game.descendants().whereType(), equals(bumpers)); + expect(game.descendants().whereType(), equals(bumpers)); bumpers.forEach(_contactedBumper); await tester.pump(); diff --git a/test/game/components/flutter_forest/flutter_forest_test.dart b/test/game/components/flutter_forest/flutter_forest_test.dart index 470719d8..ae5d86ee 100644 --- a/test/game/components/flutter_forest/flutter_forest_test.dart +++ b/test/game/components/flutter_forest/flutter_forest_test.dart @@ -36,7 +36,7 @@ class _TestGame extends Forge2DGame { value: _MockGameBloc(), children: [ FlameProvider.value( - _MockPinballPlayer(), + _MockPinballAudioPlayer(), children: [ ZCanvasComponent(children: [child]), ], @@ -47,7 +47,7 @@ class _TestGame extends Forge2DGame { } } -class _MockPinballPlayer extends Mock implements PinballPlayer {} +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} class _MockGameBloc extends Mock implements GameBloc {} @@ -91,23 +91,23 @@ void main() { ); flameTester.test( - 'three DashNestBumper', + 'three DashBumper', (game) async { final component = FlutterForest(); await game.pump(component); expect( - game.descendants().whereType().length, + game.descendants().whereType().length, equals(3), ); }, ); flameTester.test( - 'three DashNestBumpers with BumperNoiseBehavior', + 'three DashBumpers with BumperNoiseBehavior', (game) async { final component = FlutterForest(); await game.pump(component); - final bumpers = game.descendants().whereType(); + final bumpers = game.descendants().whereType(); for (final bumper in bumpers) { expect( bumper.firstChild(), diff --git a/test/game/components/game_bloc_status_listener_test.dart b/test/game/components/game_bloc_status_listener_test.dart index 7118aa8d..cc1729b8 100644 --- a/test/game/components/game_bloc_status_listener_test.dart +++ b/test/game/components/game_bloc_status_listener_test.dart @@ -8,33 +8,49 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; +import 'package:share_repository/share_repository.dart'; class _TestGame extends Forge2DGame { @override Future onLoad() async { images.prefix = ''; - await images.load(Assets.images.backbox.marquee.keyName); + await images.loadAll( + [ + const theme.DashTheme().leaderboardIcon.keyName, + Assets.images.backbox.marquee.keyName, + Assets.images.backbox.displayDivider.keyName, + ], + ); } Future pump( Iterable children, { - PinballPlayer? pinballPlayer, + PinballAudioPlayer? pinballAudioPlayer, }) async { return ensureAdd( - FlameBlocProvider.value( - value: GameBloc(), + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value( + value: GameBloc(), + ), + FlameBlocProvider.value( + value: CharacterThemeCubit(), + ), + ], children: [ MultiFlameProvider( providers: [ - FlameProvider.value( - pinballPlayer ?? _MockPinballPlayer(), + FlameProvider.value( + pinballAudioPlayer ?? _MockPinballAudioPlayer(), ), - FlameProvider.value( - const theme.DashTheme(), + FlameProvider.value( + _MockAppLocalizations(), ), ], children: children, @@ -45,11 +61,42 @@ class _TestGame extends Forge2DGame { } } -class _MockPinballPlayer extends Mock implements PinballPlayer {} +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { } +class _MockShareRepository extends Mock implements ShareRepository {} + +class _MockAppLocalizations extends Mock implements AppLocalizations { + @override + String get score => ''; + + @override + String get name => ''; + + @override + String get rank => ''; + + @override + String get enterInitials => ''; + + @override + String get arrows => ''; + + @override + String get andPress => ''; + + @override + String get enterReturn => ''; + + @override + String get toSubmit => ''; + + @override + String get loading => ''; +} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -92,54 +139,144 @@ void main() { }); group('onNewState', () { - flameTester.test( - 'changes the backbox display when the game is over', - (game) async { - final component = GameBlocStatusListener(); - final repository = _MockLeaderboardRepository(); - final backbox = Backbox(leaderboardRepository: repository); - final state = const GameState.initial() - ..copyWith( - status: GameStatus.gameOver, + group('on game over', () { + late GameState state; + + setUp(() { + state = const GameState.initial().copyWith( + status: GameStatus.gameOver, + ); + }); + + flameTester.test( + 'changes the backbox display', + (game) async { + final component = GameBlocStatusListener(); + final leaderboardRepository = _MockLeaderboardRepository(); + final shareRepository = _MockShareRepository(); + final backbox = Backbox( + leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, + entries: const [], ); - await game.pump([component, backbox]); + await game.pump([component, backbox]); - expect(() => component.onNewState(state), returnsNormally); - }, - ); + expect(() => component.onNewState(state), returnsNormally); + }, + ); - flameTester.test( - 'plays the background music on start', - (game) async { - final player = _MockPinballPlayer(); - final component = GameBlocStatusListener(); - await game.pump([component], pinballPlayer: player); + flameTester.test( + 'removes FlipperKeyControllingBehavior from Flipper', + (game) async { + final component = GameBlocStatusListener(); + final leaderboardRepository = _MockLeaderboardRepository(); + final shareRepository = _MockShareRepository(); + final backbox = Backbox( + leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, + entries: const [], + ); + final flipper = Flipper.test(side: BoardSide.left); + final behavior = FlipperKeyControllingBehavior(); - component.onNewState( - const GameState.initial().copyWith(status: GameStatus.playing), - ); + await game.pump([component, backbox, flipper]); + await flipper.ensureAdd(behavior); - verify(() => player.play(PinballAudio.backgroundMusic)).called(1); - }, - ); + expect(state.status, GameStatus.gameOver); + + component.onNewState(state); + await game.ready(); + + expect( + flipper.children.whereType(), + isEmpty, + ); + }, + ); + + flameTester.test( + 'plays the game over voice over', + (game) async { + final audioPlayer = _MockPinballAudioPlayer(); + final component = GameBlocStatusListener(); + final leaderboardRepository = _MockLeaderboardRepository(); + final shareRepository = _MockShareRepository(); + final backbox = Backbox( + leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, + entries: const [], + ); + await game.pump( + [component, backbox], + pinballAudioPlayer: audioPlayer, + ); + + component.onNewState(state); + + verify( + () => audioPlayer.play( + PinballAudio.gameOverVoiceOver, + ), + ).called(1); + }, + ); + }); - flameTester.test( - 'plays the game over voice over when it is game over', - (game) async { - final player = _MockPinballPlayer(); - final component = GameBlocStatusListener(); - final repository = _MockLeaderboardRepository(); - final backbox = Backbox(leaderboardRepository: repository); - await game.pump([component, backbox], pinballPlayer: player); - - component.onNewState( - const GameState.initial().copyWith(status: GameStatus.gameOver), + group('on playing', () { + late GameState state; + + setUp(() { + state = const GameState.initial().copyWith( + status: GameStatus.playing, ); + }); - verify(() => player.play(PinballAudio.gameOverVoiceOver)).called(1); - }, - ); + flameTester.test( + 'plays the background music on start', + (game) async { + final audioPlayer = _MockPinballAudioPlayer(); + final component = GameBlocStatusListener(); + await game.pump([component], pinballAudioPlayer: audioPlayer); + + expect(state.status, equals(GameStatus.playing)); + component.onNewState(state); + + verify( + () => audioPlayer.play( + PinballAudio.backgroundMusic, + ), + ).called(1); + }, + ); + + flameTester.test( + 'adds key controlling behavior to Flippers when the game is started', + (game) async { + final component = GameBlocStatusListener(); + final leaderboardRepository = _MockLeaderboardRepository(); + final shareRepository = _MockShareRepository(); + final backbox = Backbox( + leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, + entries: const [], + ); + final flipper = Flipper.test(side: BoardSide.left); + + await game.pump([component, backbox, flipper]); + + component.onNewState(state); + await game.ready(); + + expect( + flipper.children + .whereType() + .length, + equals(1), + ); + }, + ); + }); }); }); } diff --git a/test/game/components/golden/backbox.png b/test/game/components/golden/backbox.png index 962573ab..15426d4f 100644 Binary files a/test/game/components/golden/backbox.png and b/test/game/components/golden/backbox.png differ diff --git a/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart b/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart index 40afeb09..e23c1fd2 100644 --- a/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart +++ b/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart @@ -36,8 +36,8 @@ class _TestGame extends Forge2DGame { FlameBlocProvider.value( value: gameBloc, children: [ - FlameProvider.value( - _MockPinballPlayer(), + FlameProvider.value( + _MockPinballAudioPlayer(), children: [child], ) ], @@ -48,7 +48,7 @@ class _TestGame extends Forge2DGame { class _MockGameBloc extends Mock implements GameBloc {} -class _MockPinballPlayer extends Mock implements PinballPlayer {} +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index 4130ca77..19760f64 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -13,18 +13,20 @@ import 'package:leaderboard_repository/src/leaderboard_repository.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/src/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_theme/pinball_theme.dart' as theme; +import 'package:share_repository/share_repository.dart'; class _TestPinballGame extends PinballGame { _TestPinballGame() : super( - characterTheme: const theme.DashTheme(), + characterThemeBloc: CharacterThemeCubit(), leaderboardRepository: _MockLeaderboardRepository(), + shareRepository: _MockShareRepository(), gameBloc: GameBloc(), l10n: _MockAppLocalizations(), - player: _MockPinballPlayer(), + audioPlayer: _MockPinballAudioPlayer(), ); @override @@ -39,11 +41,12 @@ class _TestPinballGame extends PinballGame { class _TestDebugPinballGame extends DebugPinballGame { _TestDebugPinballGame() : super( - characterTheme: const theme.DashTheme(), + characterThemeBloc: CharacterThemeCubit(), leaderboardRepository: _MockLeaderboardRepository(), + shareRepository: _MockShareRepository(), gameBloc: GameBloc(), l10n: _MockAppLocalizations(), - player: _MockPinballPlayer(), + audioPlayer: _MockPinballAudioPlayer(), ); @override @@ -57,7 +60,10 @@ class _TestDebugPinballGame extends DebugPinballGame { class _MockGameBloc extends Mock implements GameBloc {} -class _MockAppLocalizations extends Mock implements AppLocalizations {} +class _MockAppLocalizations extends Mock implements AppLocalizations { + @override + String get leaderboardErrorMessage => ''; +} class _MockEventPosition extends Mock implements EventPosition {} @@ -78,7 +84,9 @@ class _MockDragEndInfo extends Mock implements DragEndInfo {} class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { } -class _MockPinballPlayer extends Mock implements PinballPlayer {} +class _MockShareRepository extends Mock implements ShareRepository {} + +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -109,6 +117,17 @@ void main() { }, ); + flameTester.test( + 'has only one CharacterSelectionBehavior', + (game) async { + await game.ready(); + expect( + game.descendants().whereType().length, + equals(1), + ); + }, + ); + flameTester.test( 'has only one Drain', (game) async { diff --git a/test/game/view/pinball_game_page_test.dart b/test/game/view/pinball_game_page_test.dart index a0ed4c7e..fce6e74e 100644 --- a/test/game/view/pinball_game_page_test.dart +++ b/test/game/view/pinball_game_page_test.dart @@ -10,30 +10,33 @@ import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/assets_manager/assets_manager.dart'; import 'package:pinball/game/game.dart'; -import 'package:pinball/gen/gen.dart'; import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/more_information/more_information.dart'; import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/start_game.dart'; import 'package:pinball_audio/pinball_audio.dart'; -import 'package:pinball_theme/pinball_theme.dart' as theme; +import 'package:share_repository/share_repository.dart'; import '../../helpers/helpers.dart'; class _TestPinballGame extends PinballGame { _TestPinballGame() : super( - characterTheme: const theme.DashTheme(), + characterThemeBloc: CharacterThemeCubit(), leaderboardRepository: _MockLeaderboardRepository(), + shareRepository: _MockShareRepository(), gameBloc: GameBloc(), l10n: _MockAppLocalizations(), - player: _MockPinballPlayer(), + audioPlayer: _MockPinballAudioPlayer(), ); @override Future onLoad() async { images.prefix = ''; - final futures = preLoadAssets(); + final futures = [ + ...preLoadAssets(), + preFetchLeaderboard(), + ]; await Future.wait(futures); return super.onLoad(); @@ -48,13 +51,18 @@ class _MockAssetsManagerCubit extends Mock implements AssetsManagerCubit {} class _MockStartGameBloc extends Mock implements StartGameBloc {} -class _MockAppLocalizations extends Mock implements AppLocalizations {} +class _MockAppLocalizations extends Mock implements AppLocalizations { + @override + String get leaderboardErrorMessage => ''; +} -class _MockPinballPlayer extends Mock implements PinballPlayer {} +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { } +class _MockShareRepository extends Mock implements ShareRepository {} + void main() { final game = _TestPinballGame(); @@ -80,14 +88,26 @@ void main() { ); }); - testWidgets('renders PinballGameView', (tester) async { - await tester.pumpApp( - PinballGamePage(), - characterThemeCubit: characterThemeCubit, - gameBloc: gameBloc, - ); + group('renders PinballGameView', () { + testWidgets('with debug mode turned on', (tester) async { + await tester.pumpApp( + PinballGamePage(), + characterThemeCubit: characterThemeCubit, + gameBloc: gameBloc, + ); - expect(find.byType(PinballGameView), findsOneWidget); + expect(find.byType(PinballGameView), findsOneWidget); + }); + + testWidgets('with debug mode turned off', (tester) async { + await tester.pumpApp( + PinballGamePage(isDebugMode: false), + characterThemeCubit: characterThemeCubit, + gameBloc: gameBloc, + ); + + expect(find.byType(PinballGameView), findsOneWidget); + }); }); testWidgets( @@ -104,9 +124,7 @@ void main() { initialState: initialAssetsState, ); await tester.pumpApp( - PinballGameView( - game: game, - ), + PinballGameView(game), assetsManagerCubit: assetsManagerCubit, characterThemeCubit: characterThemeCubit, ); @@ -136,9 +154,7 @@ void main() { ); await tester.pumpApp( - PinballGameView( - game: game, - ), + PinballGameView(game), assetsManagerCubit: assetsManagerCubit, characterThemeCubit: characterThemeCubit, gameBloc: gameBloc, @@ -149,61 +165,6 @@ void main() { expect(find.byType(PinballGameLoadedView), findsOneWidget); }); - - group('route', () { - Future pumpRoute({ - required WidgetTester tester, - required bool isDebugMode, - }) async { - await tester.pumpApp( - Scaffold( - body: Builder( - builder: (context) { - return ElevatedButton( - onPressed: () { - Navigator.of(context).push( - PinballGamePage.route( - isDebugMode: isDebugMode, - ), - ); - }, - child: const Text('Tap me'), - ); - }, - ), - ), - characterThemeCubit: characterThemeCubit, - gameBloc: gameBloc, - ); - - await tester.tap(find.text('Tap me')); - - // We can't use pumpAndSettle here because the page renders a Flame game - // which is an infinity animation, so it will timeout - await tester.pump(); // Runs the button action - await tester.pump(); // Runs the navigation - } - - testWidgets('route creates the correct non debug game', (tester) async { - await pumpRoute(tester: tester, isDebugMode: false); - expect( - find.byWidgetPredicate( - (w) => w is PinballGameView && w.game is! DebugPinballGame, - ), - findsOneWidget, - ); - }); - - testWidgets('route creates the correct debug game', (tester) async { - await pumpRoute(tester: tester, isDebugMode: true); - expect( - find.byWidgetPredicate( - (w) => w is PinballGameView && w.game is DebugPinballGame, - ), - findsOneWidget, - ); - }); - }); }); group('PinballGameView', () { @@ -228,7 +189,7 @@ void main() { testWidgets('renders game', (tester) async { await tester.pumpApp( - PinballGameView(game: game), + PinballGameView(game), gameBloc: gameBloc, startGameBloc: startGameBloc, ); @@ -256,7 +217,7 @@ void main() { ); await tester.pumpApp( - PinballGameView(game: game), + PinballGameView(game), gameBloc: gameBloc, startGameBloc: startGameBloc, ); @@ -274,7 +235,6 @@ void main() { final gameState = GameState.initial().copyWith( status: GameStatus.gameOver, ); - whenListen( startGameBloc, Stream.value(startGameState), @@ -285,17 +245,12 @@ void main() { Stream.value(gameState), initialState: gameState, ); - await tester.pumpApp( - PinballGameView(game: game), + Material(child: PinballGameView(game)), gameBloc: gameBloc, startGameBloc: startGameBloc, ); - - expect( - find.byType(GameHud), - findsNothing, - ); + expect(find.byType(GameHud), findsNothing); }); testWidgets('keep focus on game when mouse hovers over it', (tester) async { @@ -305,7 +260,6 @@ void main() { final gameState = GameState.initial().copyWith( status: GameStatus.gameOver, ); - whenListen( startGameBloc, Stream.value(startGameState), @@ -317,47 +271,51 @@ void main() { initialState: gameState, ); await tester.pumpApp( - PinballGameView(game: game), + Material(child: PinballGameView(game)), gameBloc: gameBloc, startGameBloc: startGameBloc, ); - game.focusNode.unfocus(); await tester.pump(); - expect(game.focusNode.hasFocus, isFalse); - final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); await gesture.addPointer(location: Offset.zero); addTearDown(gesture.removePointer); await gesture.moveTo((game.size / 2).toOffset()); await tester.pump(); - expect(game.focusNode.hasFocus, isTrue); }); + testWidgets('mobile controls when the overlay is added', (tester) async { + await tester.pumpApp( + PinballGameView(game), + gameBloc: gameBloc, + startGameBloc: startGameBloc, + ); + + game.overlays.add(PinballGame.mobileControlsOverlay); + + await tester.pump(); + + expect(find.byType(MobileControls), findsOneWidget); + }); + group('info icon', () { testWidgets('renders on game over', (tester) async { final gameState = GameState.initial().copyWith( status: GameStatus.gameOver, ); - whenListen( gameBloc, Stream.value(gameState), initialState: gameState, ); - await tester.pumpApp( - PinballGameView(game: game), + Material(child: PinballGameView(game)), gameBloc: gameBloc, startGameBloc: startGameBloc, ); - - expect( - find.image(Assets.images.linkBox.infoIcon), - findsOneWidget, - ); + expect(find.byIcon(Icons.info), findsOneWidget); }); testWidgets('opens MoreInformationDialog when tapped', (tester) async { @@ -370,16 +328,13 @@ void main() { initialState: gameState, ); await tester.pumpApp( - PinballGameView(game: game), + Material(child: PinballGameView(game)), gameBloc: gameBloc, startGameBloc: startGameBloc, ); await tester.tap(find.byType(IconButton)); await tester.pump(); - expect( - find.byType(MoreInformationDialog), - findsOneWidget, - ); + expect(find.byType(MoreInformationDialog), findsOneWidget); }); }); }); diff --git a/test/game/view/widgets/bonus_animation_test.dart b/test/game/view/widgets/bonus_animation_test.dart index 52c1b3d8..5f67e968 100644 --- a/test/game/view/widgets/bonus_animation_test.dart +++ b/test/game/view/widgets/bonus_animation_test.dart @@ -68,9 +68,6 @@ void main() { }); }); - // 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 callback = _MockCallback(); diff --git a/test/game/view/widgets/game_hud_test.dart b/test/game/view/widgets/game_hud_test.dart index f4054146..f4fe4b89 100644 --- a/test/game/view/widgets/game_hud_test.dart +++ b/test/game/view/widgets/game_hud_test.dart @@ -135,13 +135,9 @@ void main() { Stream.value(state), initialState: initialState, ); - await _pumpAppWithWidget(tester); await tester.pump(); - // TODO(arturplaczek): remove magic number once this is merged: - // https://github.com/flame-engine/flame/pull/1564 await Future.delayed(const Duration(seconds: 6)); - await expectLater(find.byType(ScoreView), findsOneWidget); }); }, diff --git a/test/game/view/widgets/mobile_controls_test.dart b/test/game/view/widgets/mobile_controls_test.dart new file mode 100644 index 00000000..ab9c0b76 --- /dev/null +++ b/test/game/view/widgets/mobile_controls_test.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +class _MockPinballGame extends Mock implements PinballGame {} + +extension _WidgetTesterX on WidgetTester { + Future pumpMobileControls(PinballGame game) async { + await pumpWidget( + MaterialApp( + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + ], + home: Scaffold( + body: MobileControls(game: game), + ), + ), + ); + } +} + +extension _CommonFindersX on CommonFinders { + Finder byPinballDpadDirection(PinballDpadDirection direction) { + return byWidgetPredicate((widget) { + return widget is PinballDpadButton && widget.direction == direction; + }); + } +} + +void main() { + group('MobileControls', () { + testWidgets('renders', (tester) async { + await tester.pumpMobileControls(_MockPinballGame()); + + expect(find.byType(PinballButton), findsOneWidget); + expect(find.byType(MobileDpad), findsOneWidget); + }); + + testWidgets('correctly triggers the arrow up', (tester) async { + var pressed = false; + final component = KeyboardInputController( + keyUp: { + LogicalKeyboardKey.arrowUp: () => pressed = true, + }, + ); + final game = _MockPinballGame(); + when(game.descendants).thenReturn([component]); + + await tester.pumpMobileControls(game); + await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.up)); + await tester.pump(); + + expect(pressed, isTrue); + }); + + testWidgets('correctly triggers the arrow down', (tester) async { + var pressed = false; + final component = KeyboardInputController( + keyUp: { + LogicalKeyboardKey.arrowDown: () => pressed = true, + }, + ); + final game = _MockPinballGame(); + when(game.descendants).thenReturn([component]); + + await tester.pumpMobileControls(game); + await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.down)); + await tester.pump(); + + expect(pressed, isTrue); + }); + + testWidgets('correctly triggers the arrow right', (tester) async { + var pressed = false; + final component = KeyboardInputController( + keyUp: { + LogicalKeyboardKey.arrowRight: () => pressed = true, + }, + ); + final game = _MockPinballGame(); + when(game.descendants).thenReturn([component]); + + await tester.pumpMobileControls(game); + await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.right)); + await tester.pump(); + + expect(pressed, isTrue); + }); + + testWidgets('correctly triggers the arrow left', (tester) async { + var pressed = false; + final component = KeyboardInputController( + keyUp: { + LogicalKeyboardKey.arrowLeft: () => pressed = true, + }, + ); + final game = _MockPinballGame(); + when(game.descendants).thenReturn([component]); + + await tester.pumpMobileControls(game); + await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.left)); + await tester.pump(); + + expect(pressed, isTrue); + }); + + testWidgets('correctly triggers the enter', (tester) async { + var pressed = false; + final component = KeyboardInputController( + keyUp: { + LogicalKeyboardKey.enter: () => pressed = true, + }, + ); + final game = _MockPinballGame(); + when(game.descendants).thenReturn([component]); + + await tester.pumpMobileControls(game); + await tester.tap(find.byType(PinballButton)); + await tester.pump(); + + expect(pressed, isTrue); + }); + }); +} diff --git a/test/game/view/widgets/mobile_dpad_test.dart b/test/game/view/widgets/mobile_dpad_test.dart new file mode 100644 index 00000000..2a8d0b02 --- /dev/null +++ b/test/game/view/widgets/mobile_dpad_test.dart @@ -0,0 +1,113 @@ +// ignore_for_file: one_member_abstracts + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +extension _WidgetTesterX on WidgetTester { + Future pumpDpad({ + required VoidCallback onTapUp, + required VoidCallback onTapDown, + required VoidCallback onTapLeft, + required VoidCallback onTapRight, + }) async { + await pumpWidget( + MaterialApp( + home: Scaffold( + body: MobileDpad( + onTapUp: onTapUp, + onTapDown: onTapDown, + onTapLeft: onTapLeft, + onTapRight: onTapRight, + ), + ), + ), + ); + } +} + +extension _CommonFindersX on CommonFinders { + Finder byPinballDpadDirection(PinballDpadDirection direction) { + return byWidgetPredicate((widget) { + return widget is PinballDpadButton && widget.direction == direction; + }); + } +} + +abstract class _VoidCallbackStubBase { + void onCall(); +} + +class _VoidCallbackStub extends Mock implements _VoidCallbackStubBase {} + +void main() { + group('MobileDpad', () { + testWidgets('renders correctly', (tester) async { + await tester.pumpDpad( + onTapUp: () {}, + onTapDown: () {}, + onTapLeft: () {}, + onTapRight: () {}, + ); + + expect( + find.byType(PinballDpadButton), + findsNWidgets(4), + ); + }); + + testWidgets('can tap up', (tester) async { + final stub = _VoidCallbackStub(); + await tester.pumpDpad( + onTapUp: stub.onCall, + onTapDown: () {}, + onTapLeft: () {}, + onTapRight: () {}, + ); + + await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.up)); + verify(stub.onCall).called(1); + }); + + testWidgets('can tap down', (tester) async { + final stub = _VoidCallbackStub(); + await tester.pumpDpad( + onTapUp: () {}, + onTapDown: stub.onCall, + onTapLeft: () {}, + onTapRight: () {}, + ); + + await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.down)); + verify(stub.onCall).called(1); + }); + + testWidgets('can tap left', (tester) async { + final stub = _VoidCallbackStub(); + await tester.pumpDpad( + onTapUp: () {}, + onTapDown: () {}, + onTapLeft: stub.onCall, + onTapRight: () {}, + ); + + await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.left)); + verify(stub.onCall).called(1); + }); + + testWidgets('can tap left', (tester) async { + final stub = _VoidCallbackStub(); + await tester.pumpDpad( + onTapUp: () {}, + onTapDown: () {}, + onTapLeft: () {}, + onTapRight: stub.onCall, + ); + + await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.right)); + verify(stub.onCall).called(1); + }); + }); +} diff --git a/test/game/view/widgets/replay_button_overlay_test.dart b/test/game/view/widgets/replay_button_overlay_test.dart new file mode 100644 index 00000000..1497031a --- /dev/null +++ b/test/game/view/widgets/replay_button_overlay_test.dart @@ -0,0 +1,45 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball/start_game/bloc/start_game_bloc.dart'; + +import '../../../helpers/helpers.dart'; + +class _MockStartGameBloc extends Mock implements StartGameBloc {} + +void main() { + group('ReplayButtonOverlay', () { + late StartGameBloc startGameBloc; + + setUp(() async { + await mockFlameImages(); + startGameBloc = _MockStartGameBloc(); + + whenListen( + startGameBloc, + Stream.value(const StartGameState.initial()), + initialState: const StartGameState.initial(), + ); + }); + + testWidgets('renders correctly', (tester) async { + await tester.pumpApp(const ReplayButtonOverlay()); + + expect(find.text('Replay'), findsOneWidget); + }); + + testWidgets('adds ReplayTapped event to StartGameBloc when tapped', + (tester) async { + await tester.pumpApp( + const ReplayButtonOverlay(), + startGameBloc: startGameBloc, + ); + + await tester.tap(find.text('Replay')); + await tester.pump(); + + verify(() => startGameBloc.add(const ReplayTapped())).called(1); + }); + }); +} diff --git a/test/helpers/mock_flame_images.dart b/test/helpers/mock_flame_images.dart index 48e4d40e..891ede7f 100644 --- a/test/helpers/mock_flame_images.dart +++ b/test/helpers/mock_flame_images.dart @@ -14,8 +14,6 @@ class _MockImages extends Mock implements Images {} /// Using real images blocks the tests, for this reason we need fake image /// everywhere we use [Images.fromCache] or [Images.load]. /// {@endtemplate} -// TODO(arturplaczek): need to find for a better solution for loading image -// or use original images. Future mockFlameImages() async { final image = await decodeImageFromList(Uint8List.fromList(_fakeImage)); final images = _MockImages(); diff --git a/test/helpers/pump_app.dart b/test/helpers/pump_app.dart index 45929978..df75efae 100644 --- a/test/helpers/pump_app.dart +++ b/test/helpers/pump_app.dart @@ -12,24 +12,27 @@ import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/start_game.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_ui/pinball_ui.dart'; +import 'package:share_repository/share_repository.dart'; class _MockAssetsManagerCubit extends Mock implements AssetsManagerCubit {} class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { } +class _MockShareRepository extends Mock implements ShareRepository {} + class _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {} class _MockGameBloc extends Mock implements GameBloc {} class _MockStartGameBloc extends Mock implements StartGameBloc {} -class _MockPinballPlayer extends Mock implements PinballPlayer {} +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} -PinballPlayer _buildDefaultPinballPlayer() { - final player = _MockPinballPlayer(); - when(player.load).thenAnswer((_) => [Future.value()]); - return player; +PinballAudioPlayer _buildDefaultPinballAudioPlayer() { + final audioPlayer = _MockPinballAudioPlayer(); + when(audioPlayer.load).thenAnswer((_) => [Future.value()]); + return audioPlayer; } AssetsManagerCubit _buildDefaultAssetsManagerCubit() { @@ -55,7 +58,8 @@ extension PumpApp on WidgetTester { AssetsManagerCubit? assetsManagerCubit, CharacterThemeCubit? characterThemeCubit, LeaderboardRepository? leaderboardRepository, - PinballPlayer? pinballPlayer, + ShareRepository? shareRepository, + PinballAudioPlayer? pinballAudioPlayer, }) { return runAsync(() { return pumpWidget( @@ -65,7 +69,10 @@ extension PumpApp on WidgetTester { value: leaderboardRepository ?? _MockLeaderboardRepository(), ), RepositoryProvider.value( - value: pinballPlayer ?? _buildDefaultPinballPlayer(), + value: shareRepository ?? _MockShareRepository(), + ), + RepositoryProvider.value( + value: pinballAudioPlayer ?? _buildDefaultPinballAudioPlayer(), ), ], child: MultiBlocProvider( diff --git a/test/select_character/cubit/character_theme_cubit_test.dart b/test/select_character/cubit/character_theme_cubit_test.dart index 967eb1e1..a9d03d75 100644 --- a/test/select_character/cubit/character_theme_cubit_test.dart +++ b/test/select_character/cubit/character_theme_cubit_test.dart @@ -14,7 +14,7 @@ void main() { }); blocTest( - 'charcterSelected emits selected character theme', + 'characterSelected emits selected character theme', build: CharacterThemeCubit.new, act: (bloc) => bloc.characterSelected(const SparkyTheme()), expect: () => [ diff --git a/test/start_game/bloc/start_game_bloc_test.dart b/test/start_game/bloc/start_game_bloc_test.dart index 45460fe3..3b82e83d 100644 --- a/test/start_game/bloc/start_game_bloc_test.dart +++ b/test/start_game/bloc/start_game_bloc_test.dart @@ -15,6 +15,17 @@ void main() { ], ); + blocTest( + 'on ReplayTapped changes status to selectCharacter', + build: StartGameBloc.new, + act: (bloc) => bloc.add(const ReplayTapped()), + expect: () => [ + const StartGameState( + status: StartGameStatus.selectCharacter, + ) + ], + ); + blocTest( 'on CharacterSelected changes status to howToPlay', build: StartGameBloc.new, diff --git a/test/start_game/bloc/start_game_event_test.dart b/test/start_game/bloc/start_game_event_test.dart index cf481d9f..26f96160 100644 --- a/test/start_game/bloc/start_game_event_test.dart +++ b/test/start_game/bloc/start_game_event_test.dart @@ -12,6 +12,13 @@ void main() { ); }); + test('ReplayTapped supports value equality', () { + expect( + ReplayTapped(), + equals(ReplayTapped()), + ); + }); + test('CharacterSelected supports value equality', () { expect( CharacterSelected(), diff --git a/test/start_game/bloc/start_game_state_test.dart b/test/start_game/bloc/start_game_state_test.dart index 7ede696d..ad983417 100644 --- a/test/start_game/bloc/start_game_state_test.dart +++ b/test/start_game/bloc/start_game_state_test.dart @@ -25,10 +25,41 @@ void main() { expect(testState, secondState); }); - test('supports copyWith', () { - final secondState = testState.copyWith(); + group('copyWith', () { + test( + 'copies correctly ' + 'when no argument specified', + () { + const state = StartGameState( + status: StartGameStatus.initial, + ); + expect( + state.copyWith(), + equals(state), + ); + }, + ); - expect(testState, secondState); + test( + 'copies correctly ' + 'when all arguments specified', + () { + const state = StartGameState( + status: StartGameStatus.initial, + ); + final otherState = StartGameState( + status: StartGameStatus.play, + ); + expect(state, isNot(equals(otherState))); + + expect( + state.copyWith( + status: otherState.status, + ), + equals(otherState), + ); + }, + ); }); test('has correct props', () { diff --git a/test/start_game/widgets/start_game_listener_test.dart b/test/start_game/widgets/start_game_listener_test.dart index 4e25796b..d801864b 100644 --- a/test/start_game/widgets/start_game_listener_test.dart +++ b/test/start_game/widgets/start_game_listener_test.dart @@ -16,11 +16,11 @@ class _MockGameBloc extends Mock implements GameBloc {} class _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {} -class _MockPinballPlayer extends Mock implements PinballPlayer {} +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} void main() { late StartGameBloc startGameBloc; - late PinballPlayer pinballPlayer; + late PinballAudioPlayer pinballAudioPlayer; late CharacterThemeCubit characterThemeCubit; group('StartGameListener', () { @@ -28,7 +28,7 @@ void main() { await mockFlameImages(); startGameBloc = _MockStartGameBloc(); - pinballPlayer = _MockPinballPlayer(); + pinballAudioPlayer = _MockPinballAudioPlayer(); characterThemeCubit = _MockCharacterThemeCubit(); }); @@ -46,12 +46,14 @@ void main() { }); testWidgets( - 'calls onGameStarted event', + 'calls GameStarted event', (tester) async { whenListen( startGameBloc, Stream.value( - const StartGameState(status: StartGameStatus.selectCharacter), + const StartGameState( + status: StartGameStatus.selectCharacter, + ), ), initialState: const StartGameState.initial(), ); @@ -241,7 +243,7 @@ void main() { child: SizedBox.shrink(), ), startGameBloc: startGameBloc, - pinballPlayer: pinballPlayer, + pinballAudioPlayer: pinballAudioPlayer, ); await tester.pumpAndSettle(); @@ -258,7 +260,7 @@ void main() { ); await tester.pumpAndSettle(); - verify(() => pinballPlayer.play(PinballAudio.ioPinballVoiceOver)) + verify(() => pinballAudioPlayer.play(PinballAudio.ioPinballVoiceOver)) .called(1); }, ); diff --git a/web/index.html b/web/index.html index f60ae7ce..30eb9080 100644 --- a/web/index.html +++ b/web/index.html @@ -76,6 +76,10 @@ application. For more information, see: https://developers.google.com/web/fundamentals/primers/service-workers -->