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/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/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 d00e5da1..795d04e2 100644 --- a/lib/game/bloc/game_bloc.dart +++ b/lib/game/bloc/game_bloc.dart @@ -12,7 +12,6 @@ class GameBloc extends Bloc { on(_onScored); on(_onIncreasedMultiplier); on(_onBonusActivated); - on(_onSparkyTurboChargeActivated); on(_onGameOver); on(_onGameStarted); } @@ -65,18 +64,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 a8e8593b..126ba299 100644 --- a/lib/game/bloc/game_event.dart +++ b/lib/game/bloc/game_event.dart @@ -40,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/components/backbox/backbox.dart b/lib/game/components/backbox/backbox.dart index 1c2fde6f..e455e89f 100644 --- a/lib/game/components/backbox/backbox.dart +++ b/lib/game/components/backbox/backbox.dart @@ -87,9 +87,26 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef { ), ); } else if (state is InitialsSuccessState) { - _display.add(InitialsSubmissionSuccessDisplay()); + _display.add( + GameOverInfoDisplay( + onShare: () { + _bloc.add(ShareScoreRequested(score: state.score)); + }, + ), + ); } 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 7afd34de..8a89b1bd 100644 --- a/lib/game/components/backbox/bloc/backbox_bloc.dart +++ b/lib/game/components/backbox/bloc/backbox_bloc.dart @@ -23,6 +23,7 @@ class BackboxBloc extends Bloc { ) { on(_onPlayerInitialsRequested); on(_onPlayerInitialsSubmitted); + on(_onScoreShareRequested); on(_onLeaderboardRequested); } @@ -53,13 +54,33 @@ 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 06b0a4e3..2b8a38ae 100644 --- a/lib/game/components/backbox/displays/displays.dart +++ b/lib/game/components/backbox/displays/displays.dart @@ -1,3 +1,4 @@ +export 'game_over_info_display.dart'; export 'initials_input_display.dart'; export 'initials_submission_failure_display.dart'; export 'initials_submission_success_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 68c58f43..54780c2d 100644 --- a/lib/game/components/backbox/displays/initials_input_display.dart +++ b/lib/game/components/backbox/displays/initials_input_display.dart @@ -77,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/game_assets.dart b/lib/game/game_assets.dart index 866564b4..e0b66b15 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -101,6 +101,9 @@ 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.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), diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 026471c7..89250986 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -16,7 +16,7 @@ import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; class PinballGame extends PinballForge2DGame - with HasKeyboardHandlerComponents, MultiTouchTapDetector { + with HasKeyboardHandlerComponents, MultiTouchTapDetector, HasTappables { PinballGame({ required CharacterThemeCubit characterThemeBloc, required this.leaderboardRepository, @@ -143,7 +143,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 { 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 2a04670f..0093cad2 100644 --- a/lib/game/view/widgets/widgets.dart +++ b/lib/game/view/widgets/widgets.dart @@ -3,5 +3,6 @@ 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/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index b46a42f4..8573c585 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -148,10 +148,54 @@ "@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", + "@enter": { + "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" diff --git a/lib/main_development.dart b/lib/main.dart similarity index 100% rename from lib/main_development.dart rename to lib/main.dart diff --git a/lib/main_production.dart b/lib/main_production.dart deleted file mode 100644 index 158966c8..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 pinballAudioPlayer = PinballAudioPlayer(); - unawaited( - Firebase.initializeApp().then( - (_) => authenticationRepository.authenticateAnonymously(), - ), - ); - return App( - authenticationRepository: authenticationRepository, - leaderboardRepository: leaderboardRepository, - pinballAudioPlayer: pinballAudioPlayer, - ); - }); -} diff --git a/lib/main_staging.dart b/lib/main_staging.dart deleted file mode 100644 index 158966c8..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 pinballAudioPlayer = PinballAudioPlayer(); - unawaited( - Firebase.initializeApp().then( - (_) => authenticationRepository.authenticateAnonymously(), - ), - ); - return App( - authenticationRepository: authenticationRepository, - leaderboardRepository: leaderboardRepository, - pinballAudioPlayer: pinballAudioPlayer, - ); - }); -} 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/lib/src/pinball_audio.dart b/packages/pinball_audio/lib/src/pinball_audio.dart index 0364c923..1756d965 100644 --- a/packages/pinball_audio/lib/src/pinball_audio.dart +++ b/packages/pinball_audio/lib/src/pinball_audio.dart @@ -244,7 +244,7 @@ class PinballAudioPlayer { 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_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/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index 0d603727..0288d3f4 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'); + $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,15 @@ class $AssetsImagesAndroidGen { class $AssetsImagesBackboxGen { const $AssetsImagesBackboxGen(); + /// File path: assets/images/backbox/display-divider.png AssetGenImage get displayDivider => 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 +75,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 +83,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,10 +95,15 @@ class $AssetsImagesBaseboardGen { class $AssetsImagesBoundaryGen { const $AssetsImagesBoundaryGen(); + /// File path: assets/images/boundary/bottom.png AssetGenImage get bottom => const AssetGenImage('assets/images/boundary/bottom.png'); + + /// File path: assets/images/boundary/outer-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'); } @@ -87,8 +111,10 @@ class $AssetsImagesBoundaryGen { 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,10 +123,16 @@ 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'); + + /// File path: assets/images/dino/top-wall-tunnel.png AssetGenImage get topWallTunnel => const AssetGenImage('assets/images/dino/top-wall-tunnel.png'); + + /// File path: assets/images/dino/top-wall.png AssetGenImage get topWall => const AssetGenImage('assets/images/dino/top-wall.png'); } @@ -108,10 +140,15 @@ class $AssetsImagesDinoGen { class $AssetsImagesFlapperGen { const $AssetsImagesFlapperGen(); + /// File path: assets/images/flapper/back-support.png AssetGenImage get backSupport => 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'); } @@ -119,8 +156,11 @@ class $AssetsImagesFlapperGen { 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 +192,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'); + + /// File path: assets/images/launch_ramp/foreground-railing.png AssetGenImage get foregroundRailing => 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 +208,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 +230,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,12 +242,19 @@ 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'); + + /// File path: assets/images/score/one-million.png AssetGenImage get oneMillion => 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'); + + /// File path: assets/images/score/two-hundred-thousand.png AssetGenImage get twoHundredThousand => const AssetGenImage('assets/images/score/two-hundred-thousand.png'); } @@ -204,12 +262,19 @@ class $AssetsImagesScoreGen { 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 +282,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 +302,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 +314,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 +338,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,12 +352,20 @@ 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'); + + /// 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'); + + /// File path: assets/images/android/ramp/railing-foreground.png AssetGenImage get railingForeground => const AssetGenImage('assets/images/android/ramp/railing-foreground.png'); } @@ -285,10 +373,15 @@ class $AssetsImagesAndroidRampGen { 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'); + + /// File path: assets/images/android/spaceship/saucer.png AssetGenImage get saucer => const AssetGenImage('assets/images/android/spaceship/saucer.png'); } @@ -305,8 +398,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 +410,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 +422,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 +434,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 +446,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 +458,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 +470,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 +482,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 +494,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 +506,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 +518,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 +530,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 +542,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 +554,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 +574,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 +590,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 +602,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 +614,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 +626,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 +654,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 +666,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 +678,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 +690,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 +702,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 +714,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/ball/ball.dart b/packages/pinball_components/lib/src/components/ball/ball.dart index af7a0361..806e1905 100644 --- a/packages/pinball_components/lib/src/components/ball/ball.dart +++ b/packages/pinball_components/lib/src/components/ball/ball.dart @@ -68,7 +68,7 @@ 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. 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/components.dart b/packages/pinball_components/lib/src/components/components.dart index 58319bac..17f3746a 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -13,7 +13,6 @@ export 'dash_animatronic.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/flipper.dart'; export 'google_letter/google_letter.dart'; 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 77f3dd02..00000000 --- a/packages/pinball_components/lib/src/components/fire_effect.dart +++ /dev/null @@ -1,78 +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; - -/// {@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/flipper/behaviors/flipper_jointing_behavior.dart b/packages/pinball_components/lib/src/components/flipper/behaviors/flipper_jointing_behavior.dart index eb29c181..8e487141 100644 --- 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 @@ -40,7 +40,7 @@ class _FlipperAnchor extends JointAnchor { } /// {@template flipper_anchor_revolute_joint_def} -/// Hinges one end of [Flipper] to a [_FlipperAnchor] to achieve a potivoting +/// Hinges one end of [Flipper] to a [_FlipperAnchor] to achieve a pivoting /// motion. /// {@endtemplate} class _FlipperAnchorRevoluteJointDef extends RevoluteJointDef { 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/plunger.dart b/packages/pinball_components/lib/src/components/plunger.dart index 44a91b0b..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} 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/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/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/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/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/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/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_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/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_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/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_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 index 6d929f53..a0c3e653 100644 --- a/packages/pinball_ui/lib/src/widgets/pinball_dpad_button.dart +++ b/packages/pinball_ui/lib/src/widgets/pinball_dpad_button.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:pinball_ui/gen/gen.dart'; import 'package:pinball_ui/pinball_ui.dart'; -/// Enum with all possibile directions of a [PinballDpadButton]. +/// Enum with all possible directions of a [PinballDpadButton]. enum PinballDpadDirection { /// Up up, 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..e01f5746 100644 --- a/packages/share_repository/lib/src/share_repository.dart +++ b/packages/share_repository/lib/src/share_repository.dart @@ -11,6 +11,12 @@ 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/'; + /// 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 32a412a0..1b71a25f 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 diff --git a/test/game/bloc/game_bloc_test.dart b/test/game/bloc/game_bloc_test.dart index 3e5abb74..9e8c6354 100644 --- a/test/game/bloc/game_bloc_test.dart +++ b/test/game/bloc/game_bloc_test.dart @@ -279,21 +279,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/backbox/backbox_test.dart b/test/game/components/backbox/backbox_test.dart index 5c42ea79..65474cd2 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'; @@ -21,7 +22,8 @@ import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; import 'package:platform_helper/platform_helper.dart'; -class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents { +class _TestGame extends Forge2DGame + with HasKeyboardHandlerComponents, HasTappables { final character = theme.DashTheme(); @override @@ -34,6 +36,7 @@ class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents { character.leaderboardIcon.keyName, Assets.images.backbox.marquee.keyName, Assets.images.backbox.displayDivider.keyName, + Assets.images.backbox.displayTitleDecoration.keyName, ]); } @@ -72,6 +75,8 @@ class _MockBackboxBloc extends Mock implements BackboxBloc {} class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { } +class _MockTapDownInfo extends Mock implements TapDownInfo {} + class _MockAppLocalizations extends Mock implements AppLocalizations { @override String get score => ''; @@ -100,6 +105,33 @@ class _MockAppLocalizations extends Mock implements AppLocalizations { @override String get loading => ''; + @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 => ''; } @@ -215,6 +247,28 @@ void main() { }, ); + flameTester.test( + 'adds GameOverInfoDisplay when InitialsSuccessState', + (game) async { + final state = InitialsSuccessState(score: 100); + whenListen( + bloc, + const Stream.empty(), + initialState: state, + ); + final backbox = Backbox.test( + bloc: bloc, + platformHelper: platformHelper, + ); + await game.pump(backbox); + + expect( + game.descendants().whereType().length, + equals(1), + ); + }, + ); + flameTester.test( 'adds the mobile controls overlay when platform is mobile', (game) async { @@ -246,10 +300,11 @@ void main() { flameTester.test( 'adds InitialsSubmissionSuccessDisplay on InitialsSuccessState', (game) async { + final state = InitialsSuccessState(score: 100); whenListen( bloc, - Stream.empty(), - initialState: InitialsSuccessState(), + const Stream.empty(), + initialState: state, ); final backbox = Backbox.test( bloc: bloc, @@ -258,22 +313,49 @@ void main() { await game.pump(backbox); expect( - game - .descendants() - .whereType() - .length, + 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, + 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, @@ -354,7 +436,12 @@ void main() { backbox.removeFromParent(); await game.ready(); - streamController.add(InitialsFailureState()); + streamController.add( + InitialsFailureState( + score: 10, + character: theme.DashTheme(), + ), + ); await game.ready(); expect( @@ -366,5 +453,36 @@ 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, + 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 0f265875..aec46186 100644 --- a/test/game/components/backbox/bloc/backbox_bloc_test.dart +++ b/test/game/components/backbox/bloc/backbox_bloc_test.dart @@ -82,7 +82,7 @@ void main() { ), expect: () => [ LoadingState(), - InitialsSuccessState(), + InitialsSuccessState(score: 10), ], ); @@ -113,7 +113,26 @@ 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), ], ); }); 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..c41562b0 100644 --- a/test/game/components/backbox/bloc/backbox_state_test.dart +++ b/test/game/components/backbox/bloc/backbox_state_test.dart @@ -115,20 +115,91 @@ 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), + ), + ); }); }); }); 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/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/game_bloc_status_listener_test.dart b/test/game/components/game_bloc_status_listener_test.dart index f2018fbd..3151e70b 100644 --- a/test/game/components/game_bloc_status_listener_test.dart +++ b/test/game/components/game_bloc_status_listener_test.dart @@ -162,7 +162,7 @@ void main() { ); flameTester.test( - 'removes FlipperKeyControllingBehavior from Fipper', + 'removes FlipperKeyControllingBehavior from Flipper', (game) async { final component = GameBlocStatusListener(); final repository = _MockLeaderboardRepository(); @@ -242,7 +242,7 @@ void main() { ); flameTester.test( - 'adds key controlling behavior to Fippers when the game is started', + 'adds key controlling behavior to Flippers when the game is started', (game) async { final component = GameBlocStatusListener(); final repository = _MockLeaderboardRepository(); 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/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 f4c1a5df..d801864b 100644 --- a/test/start_game/widgets/start_game_listener_test.dart +++ b/test/start_game/widgets/start_game_listener_test.dart @@ -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(), );