Merge branch 'main' into fix/spaceship-ramp-logic

pull/416/head
RuiAlonso 3 years ago
commit 356d75baf5

@ -21,7 +21,7 @@ jobs:
- name: Build Flutter App - name: Build Flutter App
run: | run: |
flutter packages get 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 - name: Deploy to Firebase
uses: FirebaseExtended/action-hosting-deploy@v0 uses: FirebaseExtended/action-hosting-deploy@v0

@ -14,3 +14,4 @@ jobs:
flutter_version: 2.10.5 flutter_version: 2.10.5
coverage_excludes: "lib/gen/*.dart" coverage_excludes: "lib/gen/*.dart"
test_optimization: false test_optimization: false

@ -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

@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="development" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="buildFlavor" value="development" />
<option name="filePath" value="$PROJECT_DIR$/lib/main_development.dart" />
<method v="2" />
</configuration>
</component>

@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="main" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
<method v="2" />
</configuration>
</component>

@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="production" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="buildFlavor" value="production" />
<option name="filePath" value="$PROJECT_DIR$/lib/main_production.dart" />
<method v="2" />
</configuration>
</component>

@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="staging" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="buildFlavor" value="staging" />
<option name="filePath" value="$PROJECT_DIR$/lib/main_staging.dart" />
<method v="2" />
</configuration>
</component>

@ -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/**"
]
}

@ -5,30 +5,10 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Launch development", "name": "Launch pinball",
"request": "launch", "request": "launch",
"type": "dart", "type": "dart",
"program": "lib/main_development.dart", "program": "lib/main.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"]
}, },
{ {
"name": "Launch component sandbox", "name": "Launch component sandbox",

@ -1,37 +1,31 @@
# Pinball # I/O Pinball
[![Pinball Header][logo]][pinball_link]
[![io_pinball][build_status_badge]][workflow_link]
![coverage][coverage_badge] ![coverage][coverage_badge]
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] [![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![License: MIT][license_badge]][license_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 ## Getting Started 🚀
- staging
- production
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 ```sh
# Development $ flutter run -d chrome
$ 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
``` ```
_\*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 ```sh
# Generate Coverage Report # Generate Coverage Report
$ genhtml coverage/lcov.info -o coverage/ $ genhtml coverage/lcov.info -o coverage/
# Open Coverage Report # Open Coverage Report
$ open coverage/index.html $ 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
...
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>es</string>
</array>
...
```
### Adding Translations ### Adding Translations
1. For each supported locale, add a new ARB file in `lib/l10n/arb`. 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 [build_status_badge]: https://github.com/flutter/pinball/actions/workflows/main.yaml/badge.svg
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
```
[coverage_badge]: coverage_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 [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 [internationalization_link]: https://flutter.dev/docs/development/accessibility-and-localization/internationalization
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg [license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license_link]: https://opensource.org/licenses/MIT [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_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_analysis_link]: https://pub.dev/packages/very_good_analysis
[very_good_cli_link]: https://github.com/VeryGoodOpenSource/very_good_cli [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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

@ -15,7 +15,7 @@ import 'package:pinball_flame/pinball_flame.dart';
/// {@endtemplate} /// {@endtemplate}
class ScoringBehavior extends Component class ScoringBehavior extends Component
with HasGameRef, FlameBlocReader<GameBloc, GameState> { with HasGameRef, FlameBlocReader<GameBloc, GameState> {
/// {@macto scoring_behavior} /// {@macro scoring_behavior}
ScoringBehavior({ ScoringBehavior({
required Points points, required Points points,
required Vector2 position, required Vector2 position,

@ -12,7 +12,6 @@ class GameBloc extends Bloc<GameEvent, GameState> {
on<Scored>(_onScored); on<Scored>(_onScored);
on<MultiplierIncreased>(_onIncreasedMultiplier); on<MultiplierIncreased>(_onIncreasedMultiplier);
on<BonusActivated>(_onBonusActivated); on<BonusActivated>(_onBonusActivated);
on<SparkyTurboChargeActivated>(_onSparkyTurboChargeActivated);
on<GameOver>(_onGameOver); on<GameOver>(_onGameOver);
on<GameStarted>(_onGameStarted); on<GameStarted>(_onGameStarted);
} }
@ -65,18 +64,4 @@ class GameBloc extends Bloc<GameEvent, GameState> {
), ),
); );
} }
Future<void> _onSparkyTurboChargeActivated(
SparkyTurboChargeActivated event,
Emitter emit,
) async {
emit(
state.copyWith(
bonusHistory: [
...state.bonusHistory,
GameBonus.sparkyTurboCharge,
],
),
);
}
} }

@ -40,13 +40,6 @@ class BonusActivated extends GameEvent {
List<Object?> get props => [bonus]; List<Object?> get props => [bonus];
} }
class SparkyTurboChargeActivated extends GameEvent {
const SparkyTurboChargeActivated();
@override
List<Object?> get props => [];
}
/// {@template multiplier_increased_game_event} /// {@template multiplier_increased_game_event}
/// Added when a multiplier is gained. /// Added when a multiplier is gained.
/// {@endtemplate} /// {@endtemplate}

@ -87,9 +87,26 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef {
), ),
); );
} else if (state is InitialsSuccessState) { } else if (state is InitialsSuccessState) {
_display.add(InitialsSubmissionSuccessDisplay()); _display.add(
GameOverInfoDisplay(
onShare: () {
_bloc.add(ShareScoreRequested(score: state.score));
},
),
);
} else if (state is InitialsFailureState) { } else if (state is InitialsFailureState) {
_display.add(InitialsSubmissionFailureDisplay()); _display.add(
InitialsSubmissionFailureDisplay(
onDismissed: () {
_bloc.add(
PlayerInitialsRequested(
score: state.score,
character: state.character,
),
);
},
),
);
} }
} }

@ -23,6 +23,7 @@ class BackboxBloc extends Bloc<BackboxEvent, BackboxState> {
) { ) {
on<PlayerInitialsRequested>(_onPlayerInitialsRequested); on<PlayerInitialsRequested>(_onPlayerInitialsRequested);
on<PlayerInitialsSubmitted>(_onPlayerInitialsSubmitted); on<PlayerInitialsSubmitted>(_onPlayerInitialsSubmitted);
on<ShareScoreRequested>(_onScoreShareRequested);
on<LeaderboardRequested>(_onLeaderboardRequested); on<LeaderboardRequested>(_onLeaderboardRequested);
} }
@ -53,13 +54,33 @@ class BackboxBloc extends Bloc<BackboxEvent, BackboxState> {
character: event.character.toType, character: event.character.toType,
), ),
); );
emit(InitialsSuccessState()); emit(
InitialsSuccessState(
score: event.score,
),
);
} catch (error, stackTrace) { } catch (error, stackTrace) {
addError(error, stackTrace); addError(error, stackTrace);
emit(InitialsFailureState()); emit(
InitialsFailureState(
score: event.score,
character: event.character,
),
);
} }
} }
Future<void> _onScoreShareRequested(
ShareScoreRequested event,
Emitter<BackboxState> emit,
) async {
emit(
ShareState(
score: event.score,
),
);
}
Future<void> _onLeaderboardRequested( Future<void> _onLeaderboardRequested(
LeaderboardRequested event, LeaderboardRequested event,
Emitter<BackboxState> emit, Emitter<BackboxState> emit,

@ -52,6 +52,22 @@ class PlayerInitialsSubmitted extends BackboxEvent {
List<Object?> get props => [score, initials, character]; List<Object?> 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<Object?> get props => [score];
}
/// Event that triggers the fetching of the leaderboard /// Event that triggers the fetching of the leaderboard
class LeaderboardRequested extends BackboxEvent { class LeaderboardRequested extends BackboxEvent {
@override @override

@ -54,14 +54,51 @@ class InitialsFormState extends BackboxState {
List<Object?> get props => [score, character]; List<Object?> 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 { class InitialsSuccessState extends BackboxState {
/// {@macro initials_success_state}
const InitialsSuccessState({
required this.score,
}) : super();
/// Player's score.
final int score;
@override @override
List<Object?> get props => []; List<Object?> get props => [score];
} }
/// State when the initials submission failed. /// State when the initials submission failed.
class InitialsFailureState extends BackboxState { 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 @override
List<Object?> get props => []; List<Object?> 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<Object?> get props => [score];
} }

@ -1,3 +1,4 @@
export 'game_over_info_display.dart';
export 'initials_input_display.dart'; export 'initials_input_display.dart';
export 'initials_submission_failure_display.dart'; export 'initials_submission_failure_display.dart';
export 'initials_submission_success_display.dart'; export 'initials_submission_success_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<void> 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<void> onLoad() async {
await super.onLoad();
text = readProvider<AppLocalizations>().shareYourScore;
}
}
class _ChallengeFriendsTextComponent extends TextComponent with HasGameRef {
_ChallengeFriendsTextComponent()
: super(
anchor: Anchor.center,
position: Vector2(0, 1.5),
textRenderer: _titleBoldTextPaint,
);
@override
Future<void> onLoad() async {
await super.onLoad();
text = readProvider<AppLocalizations>().andChallengeYourFriends;
}
}
class _TitleBackgroundSpriteComponent extends SpriteComponent with HasGameRef {
_TitleBackgroundSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2.zero(),
);
@override
Future<void> 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<void> 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<AppLocalizations>().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<void> 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<AppLocalizations>().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<void> onLoad() async {
await super.onLoad();
text = readProvider<AppLocalizations>().learnMore;
}
}
class _FirebaseTextComponent extends TextComponent with HasGameRef {
_FirebaseTextComponent()
: super(
anchor: Anchor.center,
position: Vector2(-8.5, 2.5),
textRenderer: _descriptionTextPaint,
);
@override
Future<void> onLoad() async {
await super.onLoad();
text = readProvider<AppLocalizations>().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<void> 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<AppLocalizations>().openSourceCode;
}
}

@ -77,7 +77,7 @@ class InitialsInputDisplay extends Component with HasGameRef {
); );
} }
/// Returns the current inputed initials /// Returns the current entered initials
String get initials => children String get initials => children
.whereType<InitialsLetterPrompt>() .whereType<InitialsLetterPrompt>()
.map((prompt) => prompt.char) .map((prompt) => prompt.char)

@ -1,27 +1,47 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
final _bodyTextPaint = TextPaint( final _bodyTextPaint = TextPaint(
style: const TextStyle( style: const TextStyle(
fontSize: 3, fontSize: 1.8,
color: PinballColors.white, color: PinballColors.white,
fontFamily: PinballFonts.pixeloidSans, fontFamily: PinballFonts.pixeloidSans,
fontWeight: FontWeight.w400,
), ),
); );
/// {@template initials_submission_failure_display} /// {@template initials_submission_failure_display}
/// [Backbox] display for when a failure occurs during initials submission. /// [Backbox] display for when a failure occurs during initials submission.
/// {@endtemplate} /// {@endtemplate}
class InitialsSubmissionFailureDisplay extends TextComponent { class InitialsSubmissionFailureDisplay extends Component {
/// {@macro initials_submission_failure_display}
InitialsSubmissionFailureDisplay({
required this.onDismissed,
});
final VoidCallback onDismissed;
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); final l10n = readProvider<AppLocalizations>();
position = Vector2(0, -10);
anchor = Anchor.center; await addAll([
text = 'Failure!'; ErrorComponent.bold(
textRenderer = _bodyTextPaint; 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),
]);
} }
} }

@ -101,6 +101,9 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.sparky.bumper.c.dimmed.keyName), images.load(components.Assets.images.sparky.bumper.c.dimmed.keyName),
images.load(components.Assets.images.backbox.marquee.keyName), images.load(components.Assets.images.backbox.marquee.keyName),
images.load(components.Assets.images.backbox.displayDivider.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.lit.keyName),
images.load(components.Assets.images.googleWord.letter1.dimmed.keyName), images.load(components.Assets.images.googleWord.letter1.dimmed.keyName),
images.load(components.Assets.images.googleWord.letter2.lit.keyName), images.load(components.Assets.images.googleWord.letter2.lit.keyName),

@ -16,7 +16,7 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
class PinballGame extends PinballForge2DGame class PinballGame extends PinballForge2DGame
with HasKeyboardHandlerComponents, MultiTouchTapDetector { with HasKeyboardHandlerComponents, MultiTouchTapDetector, HasTappables {
PinballGame({ PinballGame({
required CharacterThemeCubit characterThemeBloc, required CharacterThemeCubit characterThemeBloc,
required this.leaderboardRepository, required this.leaderboardRepository,
@ -143,7 +143,7 @@ class PinballGame extends PinballForge2DGame
final rocket = descendants().whereType<RocketSpriteComponent>().first; final rocket = descendants().whereType<RocketSpriteComponent>().first;
final bounds = rocket.topLeftPosition & rocket.size; 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())) { if (bounds.contains(info.eventPosition.game.toOffset())) {
descendants().whereType<Plunger>().single.pullFor(2); descendants().whereType<Plunger>().single.pullFor(2);
} else { } else {

@ -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<StartGameBloc>().add(const ReplayTapped());
},
);
}
}

@ -3,5 +3,6 @@ export 'game_hud.dart';
export 'mobile_controls.dart'; export 'mobile_controls.dart';
export 'mobile_dpad.dart'; export 'mobile_dpad.dart';
export 'play_button_overlay.dart'; export 'play_button_overlay.dart';
export 'replay_button_overlay.dart';
export 'round_count_display.dart'; export 'round_count_display.dart';
export 'score_view.dart'; export 'score_view.dart';

@ -148,10 +148,54 @@
"@loading": { "@loading": {
"description": "Text shown to indicate loading times" "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": "Enter",
"@enter": { "@enter": {
"description": "Text shown on the mobile controls enter button" "description": "Text shown on the mobile controls enter button"
}, },
"initialsErrorTitle": "Uh-oh... well, that didnt 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": "No connection. Leaderboard and sharing functionality is unavailable.",
"@leaderboardErrorMessage": { "@leaderboardErrorMessage": {
"description": "Text shown when the leaderboard had an error while loading" "description": "Text shown when the leaderboard had an error while loading"

@ -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,
);
});
}

@ -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,
);
});
}

@ -11,6 +11,7 @@ class StartGameBloc extends Bloc<StartGameEvent, StartGameState> {
/// {@macro start_game_bloc} /// {@macro start_game_bloc}
StartGameBloc() : super(const StartGameState.initial()) { StartGameBloc() : super(const StartGameState.initial()) {
on<PlayTapped>(_onPlayTapped); on<PlayTapped>(_onPlayTapped);
on<ReplayTapped>(_onReplayTapped);
on<CharacterSelected>(_onCharacterSelected); on<CharacterSelected>(_onCharacterSelected);
on<HowToPlayFinished>(_onHowToPlayFinished); on<HowToPlayFinished>(_onHowToPlayFinished);
} }
@ -26,6 +27,17 @@ class StartGameBloc extends Bloc<StartGameEvent, StartGameState> {
); );
} }
void _onReplayTapped(
ReplayTapped event,
Emitter<StartGameState> emit,
) {
emit(
state.copyWith(
status: StartGameStatus.selectCharacter,
),
);
}
void _onCharacterSelected( void _onCharacterSelected(
CharacterSelected event, CharacterSelected event,
Emitter<StartGameState> emit, Emitter<StartGameState> emit,

@ -19,6 +19,17 @@ class PlayTapped extends StartGameEvent {
List<Object> get props => []; List<Object> get props => [];
} }
/// {@template replay_tapped}
/// Replay tapped event.
/// {@endtemplate}
class ReplayTapped extends StartGameEvent {
/// {@macro replay_tapped}
const ReplayTapped();
@override
List<Object> get props => [];
}
/// {@template character_selected} /// {@template character_selected}
/// Character selected event. /// Character selected event.
/// {@endtemplate} /// {@endtemplate}

@ -244,7 +244,7 @@ class PinballAudioPlayer {
return audios.values.map((a) => a.load()).toList(); return audios.values.map((a) => a.load()).toList();
} }
/// Plays the received auido /// Plays the received audio
void play(PinballAudio audio) { void play(PinballAudio audio) {
assert( assert(
audios.containsKey(audio), audios.containsKey(audio),

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -3,6 +3,8 @@
/// FlutterGen /// FlutterGen
/// ***************************************************** /// *****************************************************
// ignore_for_file: directives_ordering,unnecessary_import
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
class $AssetsImagesGen { class $AssetsImagesGen {
@ -12,13 +14,19 @@ class $AssetsImagesGen {
$AssetsImagesBackboxGen get backbox => const $AssetsImagesBackboxGen(); $AssetsImagesBackboxGen get backbox => const $AssetsImagesBackboxGen();
$AssetsImagesBallGen get ball => const $AssetsImagesBallGen(); $AssetsImagesBallGen get ball => const $AssetsImagesBallGen();
$AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen(); $AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen();
/// File path: assets/images/board-background.png
AssetGenImage get boardBackground => AssetGenImage get boardBackground =>
const AssetGenImage('assets/images/board-background.png'); const AssetGenImage('assets/images/board-background.png');
$AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen(); $AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen();
$AssetsImagesDashGen get dash => const $AssetsImagesDashGen(); $AssetsImagesDashGen get dash => const $AssetsImagesDashGen();
$AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen(); $AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen();
/// File path: assets/images/error_background.png
AssetGenImage get errorBackground => AssetGenImage get errorBackground =>
const AssetGenImage('assets/images/error_background.png'); const AssetGenImage('assets/images/error_background.png');
$AssetsImagesFlapperGen get flapper => const $AssetsImagesFlapperGen(); $AssetsImagesFlapperGen get flapper => const $AssetsImagesFlapperGen();
$AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen(); $AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen();
$AssetsImagesGoogleWordGen get googleWord => $AssetsImagesGoogleWordGen get googleWord =>
@ -51,8 +59,15 @@ class $AssetsImagesAndroidGen {
class $AssetsImagesBackboxGen { class $AssetsImagesBackboxGen {
const $AssetsImagesBackboxGen(); const $AssetsImagesBackboxGen();
/// File path: assets/images/backbox/display-divider.png
AssetGenImage get displayDivider => AssetGenImage get displayDivider =>
const AssetGenImage('assets/images/backbox/display-divider.png'); const AssetGenImage('assets/images/backbox/display-divider.png');
/// File path: assets/images/backbox/display_title_decoration.png
AssetGenImage get displayTitleDecoration =>
const AssetGenImage('assets/images/backbox/display_title_decoration.png');
/// File path: assets/images/backbox/marquee.png
AssetGenImage get marquee => AssetGenImage get marquee =>
const AssetGenImage('assets/images/backbox/marquee.png'); const AssetGenImage('assets/images/backbox/marquee.png');
} }
@ -60,6 +75,7 @@ class $AssetsImagesBackboxGen {
class $AssetsImagesBallGen { class $AssetsImagesBallGen {
const $AssetsImagesBallGen(); const $AssetsImagesBallGen();
/// File path: assets/images/ball/flame_effect.png
AssetGenImage get flameEffect => AssetGenImage get flameEffect =>
const AssetGenImage('assets/images/ball/flame_effect.png'); const AssetGenImage('assets/images/ball/flame_effect.png');
} }
@ -67,8 +83,11 @@ class $AssetsImagesBallGen {
class $AssetsImagesBaseboardGen { class $AssetsImagesBaseboardGen {
const $AssetsImagesBaseboardGen(); const $AssetsImagesBaseboardGen();
/// File path: assets/images/baseboard/left.png
AssetGenImage get left => AssetGenImage get left =>
const AssetGenImage('assets/images/baseboard/left.png'); const AssetGenImage('assets/images/baseboard/left.png');
/// File path: assets/images/baseboard/right.png
AssetGenImage get right => AssetGenImage get right =>
const AssetGenImage('assets/images/baseboard/right.png'); const AssetGenImage('assets/images/baseboard/right.png');
} }
@ -76,10 +95,15 @@ class $AssetsImagesBaseboardGen {
class $AssetsImagesBoundaryGen { class $AssetsImagesBoundaryGen {
const $AssetsImagesBoundaryGen(); const $AssetsImagesBoundaryGen();
/// File path: assets/images/boundary/bottom.png
AssetGenImage get bottom => AssetGenImage get bottom =>
const AssetGenImage('assets/images/boundary/bottom.png'); const AssetGenImage('assets/images/boundary/bottom.png');
/// File path: assets/images/boundary/outer-bottom.png
AssetGenImage get outerBottom => AssetGenImage get outerBottom =>
const AssetGenImage('assets/images/boundary/outer-bottom.png'); const AssetGenImage('assets/images/boundary/outer-bottom.png');
/// File path: assets/images/boundary/outer.png
AssetGenImage get outer => AssetGenImage get outer =>
const AssetGenImage('assets/images/boundary/outer.png'); const AssetGenImage('assets/images/boundary/outer.png');
} }
@ -87,8 +111,10 @@ class $AssetsImagesBoundaryGen {
class $AssetsImagesDashGen { class $AssetsImagesDashGen {
const $AssetsImagesDashGen(); const $AssetsImagesDashGen();
/// File path: assets/images/dash/animatronic.png
AssetGenImage get animatronic => AssetGenImage get animatronic =>
const AssetGenImage('assets/images/dash/animatronic.png'); const AssetGenImage('assets/images/dash/animatronic.png');
$AssetsImagesDashBumperGen get bumper => const $AssetsImagesDashBumperGen(); $AssetsImagesDashBumperGen get bumper => const $AssetsImagesDashBumperGen();
} }
@ -97,10 +123,16 @@ class $AssetsImagesDinoGen {
$AssetsImagesDinoAnimatronicGen get animatronic => $AssetsImagesDinoAnimatronicGen get animatronic =>
const $AssetsImagesDinoAnimatronicGen(); const $AssetsImagesDinoAnimatronicGen();
/// File path: assets/images/dino/bottom-wall.png
AssetGenImage get bottomWall => AssetGenImage get bottomWall =>
const AssetGenImage('assets/images/dino/bottom-wall.png'); const AssetGenImage('assets/images/dino/bottom-wall.png');
/// File path: assets/images/dino/top-wall-tunnel.png
AssetGenImage get topWallTunnel => AssetGenImage get topWallTunnel =>
const AssetGenImage('assets/images/dino/top-wall-tunnel.png'); const AssetGenImage('assets/images/dino/top-wall-tunnel.png');
/// File path: assets/images/dino/top-wall.png
AssetGenImage get topWall => AssetGenImage get topWall =>
const AssetGenImage('assets/images/dino/top-wall.png'); const AssetGenImage('assets/images/dino/top-wall.png');
} }
@ -108,10 +140,15 @@ class $AssetsImagesDinoGen {
class $AssetsImagesFlapperGen { class $AssetsImagesFlapperGen {
const $AssetsImagesFlapperGen(); const $AssetsImagesFlapperGen();
/// File path: assets/images/flapper/back-support.png
AssetGenImage get backSupport => AssetGenImage get backSupport =>
const AssetGenImage('assets/images/flapper/back-support.png'); const AssetGenImage('assets/images/flapper/back-support.png');
/// File path: assets/images/flapper/flap.png
AssetGenImage get flap => AssetGenImage get flap =>
const AssetGenImage('assets/images/flapper/flap.png'); const AssetGenImage('assets/images/flapper/flap.png');
/// File path: assets/images/flapper/front-support.png
AssetGenImage get frontSupport => AssetGenImage get frontSupport =>
const AssetGenImage('assets/images/flapper/front-support.png'); const AssetGenImage('assets/images/flapper/front-support.png');
} }
@ -119,8 +156,11 @@ class $AssetsImagesFlapperGen {
class $AssetsImagesFlipperGen { class $AssetsImagesFlipperGen {
const $AssetsImagesFlipperGen(); const $AssetsImagesFlipperGen();
/// File path: assets/images/flipper/left.png
AssetGenImage get left => AssetGenImage get left =>
const AssetGenImage('assets/images/flipper/left.png'); const AssetGenImage('assets/images/flipper/left.png');
/// File path: assets/images/flipper/right.png
AssetGenImage get right => AssetGenImage get right =>
const AssetGenImage('assets/images/flipper/right.png'); const AssetGenImage('assets/images/flipper/right.png');
} }
@ -152,10 +192,15 @@ class $AssetsImagesKickerGen {
class $AssetsImagesLaunchRampGen { class $AssetsImagesLaunchRampGen {
const $AssetsImagesLaunchRampGen(); const $AssetsImagesLaunchRampGen();
/// File path: assets/images/launch_ramp/background-railing.png
AssetGenImage get backgroundRailing => AssetGenImage get backgroundRailing =>
const AssetGenImage('assets/images/launch_ramp/background-railing.png'); const AssetGenImage('assets/images/launch_ramp/background-railing.png');
/// File path: assets/images/launch_ramp/foreground-railing.png
AssetGenImage get foregroundRailing => AssetGenImage get foregroundRailing =>
const AssetGenImage('assets/images/launch_ramp/foreground-railing.png'); const AssetGenImage('assets/images/launch_ramp/foreground-railing.png');
/// File path: assets/images/launch_ramp/ramp.png
AssetGenImage get ramp => AssetGenImage get ramp =>
const AssetGenImage('assets/images/launch_ramp/ramp.png'); const AssetGenImage('assets/images/launch_ramp/ramp.png');
} }
@ -163,8 +208,11 @@ class $AssetsImagesLaunchRampGen {
class $AssetsImagesMultiballGen { class $AssetsImagesMultiballGen {
const $AssetsImagesMultiballGen(); const $AssetsImagesMultiballGen();
/// File path: assets/images/multiball/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/multiball/dimmed.png'); const AssetGenImage('assets/images/multiball/dimmed.png');
/// File path: assets/images/multiball/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/multiball/lit.png'); const AssetGenImage('assets/images/multiball/lit.png');
} }
@ -182,8 +230,11 @@ class $AssetsImagesMultiplierGen {
class $AssetsImagesPlungerGen { class $AssetsImagesPlungerGen {
const $AssetsImagesPlungerGen(); const $AssetsImagesPlungerGen();
/// File path: assets/images/plunger/plunger.png
AssetGenImage get plunger => AssetGenImage get plunger =>
const AssetGenImage('assets/images/plunger/plunger.png'); const AssetGenImage('assets/images/plunger/plunger.png');
/// File path: assets/images/plunger/rocket.png
AssetGenImage get rocket => AssetGenImage get rocket =>
const AssetGenImage('assets/images/plunger/rocket.png'); const AssetGenImage('assets/images/plunger/rocket.png');
} }
@ -191,12 +242,19 @@ class $AssetsImagesPlungerGen {
class $AssetsImagesScoreGen { class $AssetsImagesScoreGen {
const $AssetsImagesScoreGen(); const $AssetsImagesScoreGen();
/// File path: assets/images/score/five-thousand.png
AssetGenImage get fiveThousand => AssetGenImage get fiveThousand =>
const AssetGenImage('assets/images/score/five-thousand.png'); const AssetGenImage('assets/images/score/five-thousand.png');
/// File path: assets/images/score/one-million.png
AssetGenImage get oneMillion => AssetGenImage get oneMillion =>
const AssetGenImage('assets/images/score/one-million.png'); const AssetGenImage('assets/images/score/one-million.png');
/// File path: assets/images/score/twenty-thousand.png
AssetGenImage get twentyThousand => AssetGenImage get twentyThousand =>
const AssetGenImage('assets/images/score/twenty-thousand.png'); const AssetGenImage('assets/images/score/twenty-thousand.png');
/// File path: assets/images/score/two-hundred-thousand.png
AssetGenImage get twoHundredThousand => AssetGenImage get twoHundredThousand =>
const AssetGenImage('assets/images/score/two-hundred-thousand.png'); const AssetGenImage('assets/images/score/two-hundred-thousand.png');
} }
@ -204,12 +262,19 @@ class $AssetsImagesScoreGen {
class $AssetsImagesSignpostGen { class $AssetsImagesSignpostGen {
const $AssetsImagesSignpostGen(); const $AssetsImagesSignpostGen();
/// File path: assets/images/signpost/active1.png
AssetGenImage get active1 => AssetGenImage get active1 =>
const AssetGenImage('assets/images/signpost/active1.png'); const AssetGenImage('assets/images/signpost/active1.png');
/// File path: assets/images/signpost/active2.png
AssetGenImage get active2 => AssetGenImage get active2 =>
const AssetGenImage('assets/images/signpost/active2.png'); const AssetGenImage('assets/images/signpost/active2.png');
/// File path: assets/images/signpost/active3.png
AssetGenImage get active3 => AssetGenImage get active3 =>
const AssetGenImage('assets/images/signpost/active3.png'); const AssetGenImage('assets/images/signpost/active3.png');
/// File path: assets/images/signpost/inactive.png
AssetGenImage get inactive => AssetGenImage get inactive =>
const AssetGenImage('assets/images/signpost/inactive.png'); const AssetGenImage('assets/images/signpost/inactive.png');
} }
@ -217,12 +282,19 @@ class $AssetsImagesSignpostGen {
class $AssetsImagesSkillShotGen { class $AssetsImagesSkillShotGen {
const $AssetsImagesSkillShotGen(); const $AssetsImagesSkillShotGen();
/// File path: assets/images/skill_shot/decal.png
AssetGenImage get decal => AssetGenImage get decal =>
const AssetGenImage('assets/images/skill_shot/decal.png'); const AssetGenImage('assets/images/skill_shot/decal.png');
/// File path: assets/images/skill_shot/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/skill_shot/dimmed.png'); const AssetGenImage('assets/images/skill_shot/dimmed.png');
/// File path: assets/images/skill_shot/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/skill_shot/lit.png'); const AssetGenImage('assets/images/skill_shot/lit.png');
/// File path: assets/images/skill_shot/pin.png
AssetGenImage get pin => AssetGenImage get pin =>
const AssetGenImage('assets/images/skill_shot/pin.png'); const AssetGenImage('assets/images/skill_shot/pin.png');
} }
@ -230,8 +302,11 @@ class $AssetsImagesSkillShotGen {
class $AssetsImagesSlingshotGen { class $AssetsImagesSlingshotGen {
const $AssetsImagesSlingshotGen(); const $AssetsImagesSlingshotGen();
/// File path: assets/images/slingshot/lower.png
AssetGenImage get lower => AssetGenImage get lower =>
const AssetGenImage('assets/images/slingshot/lower.png'); const AssetGenImage('assets/images/slingshot/lower.png');
/// File path: assets/images/slingshot/upper.png
AssetGenImage get upper => AssetGenImage get upper =>
const AssetGenImage('assets/images/slingshot/upper.png'); const AssetGenImage('assets/images/slingshot/upper.png');
} }
@ -239,8 +314,10 @@ class $AssetsImagesSlingshotGen {
class $AssetsImagesSparkyGen { class $AssetsImagesSparkyGen {
const $AssetsImagesSparkyGen(); const $AssetsImagesSparkyGen();
/// File path: assets/images/sparky/animatronic.png
AssetGenImage get animatronic => AssetGenImage get animatronic =>
const AssetGenImage('assets/images/sparky/animatronic.png'); const AssetGenImage('assets/images/sparky/animatronic.png');
$AssetsImagesSparkyBumperGen get bumper => $AssetsImagesSparkyBumperGen get bumper =>
const $AssetsImagesSparkyBumperGen(); const $AssetsImagesSparkyBumperGen();
$AssetsImagesSparkyComputerGen get computer => $AssetsImagesSparkyComputerGen get computer =>
@ -261,8 +338,11 @@ class $AssetsImagesAndroidBumperGen {
class $AssetsImagesAndroidRailGen { class $AssetsImagesAndroidRailGen {
const $AssetsImagesAndroidRailGen(); const $AssetsImagesAndroidRailGen();
/// File path: assets/images/android/rail/exit.png
AssetGenImage get exit => AssetGenImage get exit =>
const AssetGenImage('assets/images/android/rail/exit.png'); const AssetGenImage('assets/images/android/rail/exit.png');
/// File path: assets/images/android/rail/main.png
AssetGenImage get main => AssetGenImage get main =>
const AssetGenImage('assets/images/android/rail/main.png'); const AssetGenImage('assets/images/android/rail/main.png');
} }
@ -272,12 +352,20 @@ class $AssetsImagesAndroidRampGen {
$AssetsImagesAndroidRampArrowGen get arrow => $AssetsImagesAndroidRampArrowGen get arrow =>
const $AssetsImagesAndroidRampArrowGen(); const $AssetsImagesAndroidRampArrowGen();
/// File path: assets/images/android/ramp/board-opening.png
AssetGenImage get boardOpening => AssetGenImage get boardOpening =>
const AssetGenImage('assets/images/android/ramp/board-opening.png'); const AssetGenImage('assets/images/android/ramp/board-opening.png');
/// File path: assets/images/android/ramp/main.png
AssetGenImage get main => AssetGenImage get main =>
const AssetGenImage('assets/images/android/ramp/main.png'); const AssetGenImage('assets/images/android/ramp/main.png');
/// File path: assets/images/android/ramp/railing-background.png
AssetGenImage get railingBackground => AssetGenImage get railingBackground =>
const AssetGenImage('assets/images/android/ramp/railing-background.png'); const AssetGenImage('assets/images/android/ramp/railing-background.png');
/// File path: assets/images/android/ramp/railing-foreground.png
AssetGenImage get railingForeground => AssetGenImage get railingForeground =>
const AssetGenImage('assets/images/android/ramp/railing-foreground.png'); const AssetGenImage('assets/images/android/ramp/railing-foreground.png');
} }
@ -285,10 +373,15 @@ class $AssetsImagesAndroidRampGen {
class $AssetsImagesAndroidSpaceshipGen { class $AssetsImagesAndroidSpaceshipGen {
const $AssetsImagesAndroidSpaceshipGen(); const $AssetsImagesAndroidSpaceshipGen();
/// File path: assets/images/android/spaceship/animatronic.png
AssetGenImage get animatronic => AssetGenImage get animatronic =>
const AssetGenImage('assets/images/android/spaceship/animatronic.png'); const AssetGenImage('assets/images/android/spaceship/animatronic.png');
/// File path: assets/images/android/spaceship/light-beam.png
AssetGenImage get lightBeam => AssetGenImage get lightBeam =>
const AssetGenImage('assets/images/android/spaceship/light-beam.png'); const AssetGenImage('assets/images/android/spaceship/light-beam.png');
/// File path: assets/images/android/spaceship/saucer.png
AssetGenImage get saucer => AssetGenImage get saucer =>
const AssetGenImage('assets/images/android/spaceship/saucer.png'); const AssetGenImage('assets/images/android/spaceship/saucer.png');
} }
@ -305,8 +398,11 @@ class $AssetsImagesDashBumperGen {
class $AssetsImagesDinoAnimatronicGen { class $AssetsImagesDinoAnimatronicGen {
const $AssetsImagesDinoAnimatronicGen(); const $AssetsImagesDinoAnimatronicGen();
/// File path: assets/images/dino/animatronic/head.png
AssetGenImage get head => AssetGenImage get head =>
const AssetGenImage('assets/images/dino/animatronic/head.png'); const AssetGenImage('assets/images/dino/animatronic/head.png');
/// File path: assets/images/dino/animatronic/mouth.png
AssetGenImage get mouth => AssetGenImage get mouth =>
const AssetGenImage('assets/images/dino/animatronic/mouth.png'); const AssetGenImage('assets/images/dino/animatronic/mouth.png');
} }
@ -314,8 +410,11 @@ class $AssetsImagesDinoAnimatronicGen {
class $AssetsImagesGoogleWordLetter1Gen { class $AssetsImagesGoogleWordLetter1Gen {
const $AssetsImagesGoogleWordLetter1Gen(); const $AssetsImagesGoogleWordLetter1Gen();
/// File path: assets/images/google_word/letter1/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/google_word/letter1/dimmed.png'); const AssetGenImage('assets/images/google_word/letter1/dimmed.png');
/// File path: assets/images/google_word/letter1/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/google_word/letter1/lit.png'); const AssetGenImage('assets/images/google_word/letter1/lit.png');
} }
@ -323,8 +422,11 @@ class $AssetsImagesGoogleWordLetter1Gen {
class $AssetsImagesGoogleWordLetter2Gen { class $AssetsImagesGoogleWordLetter2Gen {
const $AssetsImagesGoogleWordLetter2Gen(); const $AssetsImagesGoogleWordLetter2Gen();
/// File path: assets/images/google_word/letter2/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/google_word/letter2/dimmed.png'); const AssetGenImage('assets/images/google_word/letter2/dimmed.png');
/// File path: assets/images/google_word/letter2/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/google_word/letter2/lit.png'); const AssetGenImage('assets/images/google_word/letter2/lit.png');
} }
@ -332,8 +434,11 @@ class $AssetsImagesGoogleWordLetter2Gen {
class $AssetsImagesGoogleWordLetter3Gen { class $AssetsImagesGoogleWordLetter3Gen {
const $AssetsImagesGoogleWordLetter3Gen(); const $AssetsImagesGoogleWordLetter3Gen();
/// File path: assets/images/google_word/letter3/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/google_word/letter3/dimmed.png'); const AssetGenImage('assets/images/google_word/letter3/dimmed.png');
/// File path: assets/images/google_word/letter3/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/google_word/letter3/lit.png'); const AssetGenImage('assets/images/google_word/letter3/lit.png');
} }
@ -341,8 +446,11 @@ class $AssetsImagesGoogleWordLetter3Gen {
class $AssetsImagesGoogleWordLetter4Gen { class $AssetsImagesGoogleWordLetter4Gen {
const $AssetsImagesGoogleWordLetter4Gen(); const $AssetsImagesGoogleWordLetter4Gen();
/// File path: assets/images/google_word/letter4/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/google_word/letter4/dimmed.png'); const AssetGenImage('assets/images/google_word/letter4/dimmed.png');
/// File path: assets/images/google_word/letter4/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/google_word/letter4/lit.png'); const AssetGenImage('assets/images/google_word/letter4/lit.png');
} }
@ -350,8 +458,11 @@ class $AssetsImagesGoogleWordLetter4Gen {
class $AssetsImagesGoogleWordLetter5Gen { class $AssetsImagesGoogleWordLetter5Gen {
const $AssetsImagesGoogleWordLetter5Gen(); const $AssetsImagesGoogleWordLetter5Gen();
/// File path: assets/images/google_word/letter5/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/google_word/letter5/dimmed.png'); const AssetGenImage('assets/images/google_word/letter5/dimmed.png');
/// File path: assets/images/google_word/letter5/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/google_word/letter5/lit.png'); const AssetGenImage('assets/images/google_word/letter5/lit.png');
} }
@ -359,8 +470,11 @@ class $AssetsImagesGoogleWordLetter5Gen {
class $AssetsImagesGoogleWordLetter6Gen { class $AssetsImagesGoogleWordLetter6Gen {
const $AssetsImagesGoogleWordLetter6Gen(); const $AssetsImagesGoogleWordLetter6Gen();
/// File path: assets/images/google_word/letter6/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/google_word/letter6/dimmed.png'); const AssetGenImage('assets/images/google_word/letter6/dimmed.png');
/// File path: assets/images/google_word/letter6/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/google_word/letter6/lit.png'); const AssetGenImage('assets/images/google_word/letter6/lit.png');
} }
@ -368,8 +482,11 @@ class $AssetsImagesGoogleWordLetter6Gen {
class $AssetsImagesKickerLeftGen { class $AssetsImagesKickerLeftGen {
const $AssetsImagesKickerLeftGen(); const $AssetsImagesKickerLeftGen();
/// File path: assets/images/kicker/left/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/kicker/left/dimmed.png'); const AssetGenImage('assets/images/kicker/left/dimmed.png');
/// File path: assets/images/kicker/left/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/kicker/left/lit.png'); const AssetGenImage('assets/images/kicker/left/lit.png');
} }
@ -377,8 +494,11 @@ class $AssetsImagesKickerLeftGen {
class $AssetsImagesKickerRightGen { class $AssetsImagesKickerRightGen {
const $AssetsImagesKickerRightGen(); const $AssetsImagesKickerRightGen();
/// File path: assets/images/kicker/right/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/kicker/right/dimmed.png'); const AssetGenImage('assets/images/kicker/right/dimmed.png');
/// File path: assets/images/kicker/right/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/kicker/right/lit.png'); const AssetGenImage('assets/images/kicker/right/lit.png');
} }
@ -386,8 +506,11 @@ class $AssetsImagesKickerRightGen {
class $AssetsImagesMultiplierX2Gen { class $AssetsImagesMultiplierX2Gen {
const $AssetsImagesMultiplierX2Gen(); const $AssetsImagesMultiplierX2Gen();
/// File path: assets/images/multiplier/x2/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/multiplier/x2/dimmed.png'); const AssetGenImage('assets/images/multiplier/x2/dimmed.png');
/// File path: assets/images/multiplier/x2/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/multiplier/x2/lit.png'); const AssetGenImage('assets/images/multiplier/x2/lit.png');
} }
@ -395,8 +518,11 @@ class $AssetsImagesMultiplierX2Gen {
class $AssetsImagesMultiplierX3Gen { class $AssetsImagesMultiplierX3Gen {
const $AssetsImagesMultiplierX3Gen(); const $AssetsImagesMultiplierX3Gen();
/// File path: assets/images/multiplier/x3/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/multiplier/x3/dimmed.png'); const AssetGenImage('assets/images/multiplier/x3/dimmed.png');
/// File path: assets/images/multiplier/x3/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/multiplier/x3/lit.png'); const AssetGenImage('assets/images/multiplier/x3/lit.png');
} }
@ -404,8 +530,11 @@ class $AssetsImagesMultiplierX3Gen {
class $AssetsImagesMultiplierX4Gen { class $AssetsImagesMultiplierX4Gen {
const $AssetsImagesMultiplierX4Gen(); const $AssetsImagesMultiplierX4Gen();
/// File path: assets/images/multiplier/x4/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/multiplier/x4/dimmed.png'); const AssetGenImage('assets/images/multiplier/x4/dimmed.png');
/// File path: assets/images/multiplier/x4/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/multiplier/x4/lit.png'); const AssetGenImage('assets/images/multiplier/x4/lit.png');
} }
@ -413,8 +542,11 @@ class $AssetsImagesMultiplierX4Gen {
class $AssetsImagesMultiplierX5Gen { class $AssetsImagesMultiplierX5Gen {
const $AssetsImagesMultiplierX5Gen(); const $AssetsImagesMultiplierX5Gen();
/// File path: assets/images/multiplier/x5/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/multiplier/x5/dimmed.png'); const AssetGenImage('assets/images/multiplier/x5/dimmed.png');
/// File path: assets/images/multiplier/x5/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/multiplier/x5/lit.png'); const AssetGenImage('assets/images/multiplier/x5/lit.png');
} }
@ -422,8 +554,11 @@ class $AssetsImagesMultiplierX5Gen {
class $AssetsImagesMultiplierX6Gen { class $AssetsImagesMultiplierX6Gen {
const $AssetsImagesMultiplierX6Gen(); const $AssetsImagesMultiplierX6Gen();
/// File path: assets/images/multiplier/x6/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/multiplier/x6/dimmed.png'); const AssetGenImage('assets/images/multiplier/x6/dimmed.png');
/// File path: assets/images/multiplier/x6/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/multiplier/x6/lit.png'); const AssetGenImage('assets/images/multiplier/x6/lit.png');
} }
@ -439,10 +574,15 @@ class $AssetsImagesSparkyBumperGen {
class $AssetsImagesSparkyComputerGen { class $AssetsImagesSparkyComputerGen {
const $AssetsImagesSparkyComputerGen(); const $AssetsImagesSparkyComputerGen();
/// File path: assets/images/sparky/computer/base.png
AssetGenImage get base => AssetGenImage get base =>
const AssetGenImage('assets/images/sparky/computer/base.png'); const AssetGenImage('assets/images/sparky/computer/base.png');
/// File path: assets/images/sparky/computer/glow.png
AssetGenImage get glow => AssetGenImage get glow =>
const AssetGenImage('assets/images/sparky/computer/glow.png'); const AssetGenImage('assets/images/sparky/computer/glow.png');
/// File path: assets/images/sparky/computer/top.png
AssetGenImage get top => AssetGenImage get top =>
const AssetGenImage('assets/images/sparky/computer/top.png'); const AssetGenImage('assets/images/sparky/computer/top.png');
} }
@ -450,8 +590,11 @@ class $AssetsImagesSparkyComputerGen {
class $AssetsImagesAndroidBumperAGen { class $AssetsImagesAndroidBumperAGen {
const $AssetsImagesAndroidBumperAGen(); const $AssetsImagesAndroidBumperAGen();
/// File path: assets/images/android/bumper/a/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/android/bumper/a/dimmed.png'); const AssetGenImage('assets/images/android/bumper/a/dimmed.png');
/// File path: assets/images/android/bumper/a/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/android/bumper/a/lit.png'); const AssetGenImage('assets/images/android/bumper/a/lit.png');
} }
@ -459,8 +602,11 @@ class $AssetsImagesAndroidBumperAGen {
class $AssetsImagesAndroidBumperBGen { class $AssetsImagesAndroidBumperBGen {
const $AssetsImagesAndroidBumperBGen(); const $AssetsImagesAndroidBumperBGen();
/// File path: assets/images/android/bumper/b/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/android/bumper/b/dimmed.png'); const AssetGenImage('assets/images/android/bumper/b/dimmed.png');
/// File path: assets/images/android/bumper/b/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/android/bumper/b/lit.png'); const AssetGenImage('assets/images/android/bumper/b/lit.png');
} }
@ -468,8 +614,11 @@ class $AssetsImagesAndroidBumperBGen {
class $AssetsImagesAndroidBumperCowGen { class $AssetsImagesAndroidBumperCowGen {
const $AssetsImagesAndroidBumperCowGen(); const $AssetsImagesAndroidBumperCowGen();
/// File path: assets/images/android/bumper/cow/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/android/bumper/cow/dimmed.png'); const AssetGenImage('assets/images/android/bumper/cow/dimmed.png');
/// File path: assets/images/android/bumper/cow/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/android/bumper/cow/lit.png'); const AssetGenImage('assets/images/android/bumper/cow/lit.png');
} }
@ -477,16 +626,27 @@ class $AssetsImagesAndroidBumperCowGen {
class $AssetsImagesAndroidRampArrowGen { class $AssetsImagesAndroidRampArrowGen {
const $AssetsImagesAndroidRampArrowGen(); const $AssetsImagesAndroidRampArrowGen();
/// File path: assets/images/android/ramp/arrow/active1.png
AssetGenImage get active1 => AssetGenImage get active1 =>
const AssetGenImage('assets/images/android/ramp/arrow/active1.png'); const AssetGenImage('assets/images/android/ramp/arrow/active1.png');
/// File path: assets/images/android/ramp/arrow/active2.png
AssetGenImage get active2 => AssetGenImage get active2 =>
const AssetGenImage('assets/images/android/ramp/arrow/active2.png'); const AssetGenImage('assets/images/android/ramp/arrow/active2.png');
/// File path: assets/images/android/ramp/arrow/active3.png
AssetGenImage get active3 => AssetGenImage get active3 =>
const AssetGenImage('assets/images/android/ramp/arrow/active3.png'); const AssetGenImage('assets/images/android/ramp/arrow/active3.png');
/// File path: assets/images/android/ramp/arrow/active4.png
AssetGenImage get active4 => AssetGenImage get active4 =>
const AssetGenImage('assets/images/android/ramp/arrow/active4.png'); const AssetGenImage('assets/images/android/ramp/arrow/active4.png');
/// File path: assets/images/android/ramp/arrow/active5.png
AssetGenImage get active5 => AssetGenImage get active5 =>
const AssetGenImage('assets/images/android/ramp/arrow/active5.png'); const AssetGenImage('assets/images/android/ramp/arrow/active5.png');
/// File path: assets/images/android/ramp/arrow/inactive.png
AssetGenImage get inactive => AssetGenImage get inactive =>
const AssetGenImage('assets/images/android/ramp/arrow/inactive.png'); const AssetGenImage('assets/images/android/ramp/arrow/inactive.png');
} }
@ -494,8 +654,11 @@ class $AssetsImagesAndroidRampArrowGen {
class $AssetsImagesDashBumperAGen { class $AssetsImagesDashBumperAGen {
const $AssetsImagesDashBumperAGen(); const $AssetsImagesDashBumperAGen();
/// File path: assets/images/dash/bumper/a/active.png
AssetGenImage get active => AssetGenImage get active =>
const AssetGenImage('assets/images/dash/bumper/a/active.png'); const AssetGenImage('assets/images/dash/bumper/a/active.png');
/// File path: assets/images/dash/bumper/a/inactive.png
AssetGenImage get inactive => AssetGenImage get inactive =>
const AssetGenImage('assets/images/dash/bumper/a/inactive.png'); const AssetGenImage('assets/images/dash/bumper/a/inactive.png');
} }
@ -503,8 +666,11 @@ class $AssetsImagesDashBumperAGen {
class $AssetsImagesDashBumperBGen { class $AssetsImagesDashBumperBGen {
const $AssetsImagesDashBumperBGen(); const $AssetsImagesDashBumperBGen();
/// File path: assets/images/dash/bumper/b/active.png
AssetGenImage get active => AssetGenImage get active =>
const AssetGenImage('assets/images/dash/bumper/b/active.png'); const AssetGenImage('assets/images/dash/bumper/b/active.png');
/// File path: assets/images/dash/bumper/b/inactive.png
AssetGenImage get inactive => AssetGenImage get inactive =>
const AssetGenImage('assets/images/dash/bumper/b/inactive.png'); const AssetGenImage('assets/images/dash/bumper/b/inactive.png');
} }
@ -512,8 +678,11 @@ class $AssetsImagesDashBumperBGen {
class $AssetsImagesDashBumperMainGen { class $AssetsImagesDashBumperMainGen {
const $AssetsImagesDashBumperMainGen(); const $AssetsImagesDashBumperMainGen();
/// File path: assets/images/dash/bumper/main/active.png
AssetGenImage get active => AssetGenImage get active =>
const AssetGenImage('assets/images/dash/bumper/main/active.png'); const AssetGenImage('assets/images/dash/bumper/main/active.png');
/// File path: assets/images/dash/bumper/main/inactive.png
AssetGenImage get inactive => AssetGenImage get inactive =>
const AssetGenImage('assets/images/dash/bumper/main/inactive.png'); const AssetGenImage('assets/images/dash/bumper/main/inactive.png');
} }
@ -521,8 +690,11 @@ class $AssetsImagesDashBumperMainGen {
class $AssetsImagesSparkyBumperAGen { class $AssetsImagesSparkyBumperAGen {
const $AssetsImagesSparkyBumperAGen(); const $AssetsImagesSparkyBumperAGen();
/// File path: assets/images/sparky/bumper/a/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/sparky/bumper/a/dimmed.png'); const AssetGenImage('assets/images/sparky/bumper/a/dimmed.png');
/// File path: assets/images/sparky/bumper/a/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/sparky/bumper/a/lit.png'); const AssetGenImage('assets/images/sparky/bumper/a/lit.png');
} }
@ -530,8 +702,11 @@ class $AssetsImagesSparkyBumperAGen {
class $AssetsImagesSparkyBumperBGen { class $AssetsImagesSparkyBumperBGen {
const $AssetsImagesSparkyBumperBGen(); const $AssetsImagesSparkyBumperBGen();
/// File path: assets/images/sparky/bumper/b/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/sparky/bumper/b/dimmed.png'); const AssetGenImage('assets/images/sparky/bumper/b/dimmed.png');
/// File path: assets/images/sparky/bumper/b/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/sparky/bumper/b/lit.png'); const AssetGenImage('assets/images/sparky/bumper/b/lit.png');
} }
@ -539,8 +714,11 @@ class $AssetsImagesSparkyBumperBGen {
class $AssetsImagesSparkyBumperCGen { class $AssetsImagesSparkyBumperCGen {
const $AssetsImagesSparkyBumperCGen(); const $AssetsImagesSparkyBumperCGen();
/// File path: assets/images/sparky/bumper/c/dimmed.png
AssetGenImage get dimmed => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/sparky/bumper/c/dimmed.png'); const AssetGenImage('assets/images/sparky/bumper/c/dimmed.png');
/// File path: assets/images/sparky/bumper/c/lit.png
AssetGenImage get lit => AssetGenImage get lit =>
const AssetGenImage('assets/images/sparky/bumper/c/lit.png'); const AssetGenImage('assets/images/sparky/bumper/c/lit.png');
} }

@ -3,9 +3,14 @@
/// FlutterGen /// FlutterGen
/// ***************************************************** /// *****************************************************
// ignore_for_file: directives_ordering,unnecessary_import
class FontFamily { class FontFamily {
FontFamily._(); FontFamily._();
/// Font family: PixeloidMono
static const String pixeloidMono = 'PixeloidMono'; static const String pixeloidMono = 'PixeloidMono';
/// Font family: PixeloidSans
static const String pixeloidSans = 'PixeloidSans'; static const String pixeloidSans = 'PixeloidSans';
} }

@ -68,7 +68,7 @@ class Ball extends BodyComponent with Layered, InitialPosition, ZIndex {
return world.createBody(bodyDef)..createFixtureFromShape(shape, 1); 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 /// The [Ball] will no longer be affected by any forces, including it's
/// weight and those emitted from collisions. /// weight and those emitted from collisions.

@ -12,7 +12,7 @@ class BumpingBehavior extends ContactBehavior {
/// Determines how strong the bump is. /// Determines how strong the bump is.
final double _strength; 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. /// coordinates.
@visibleForTesting @visibleForTesting
final WorldManifold worldManifold = WorldManifold(); final WorldManifold worldManifold = WorldManifold();

@ -13,7 +13,6 @@ export 'dash_animatronic.dart';
export 'dash_bumper/dash_bumper.dart'; export 'dash_bumper/dash_bumper.dart';
export 'dino_walls.dart'; export 'dino_walls.dart';
export 'error_component.dart'; export 'error_component.dart';
export 'fire_effect.dart';
export 'flapper/flapper.dart'; export 'flapper/flapper.dart';
export 'flipper/flipper.dart'; export 'flipper/flipper.dart';
export 'google_letter/google_letter.dart'; export 'google_letter/google_letter.dart';

@ -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<void> 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<double>(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)],
);
},
);
}
}

@ -40,7 +40,7 @@ class _FlipperAnchor extends JointAnchor {
} }
/// {@template flipper_anchor_revolute_joint_def} /// {@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. /// motion.
/// {@endtemplate} /// {@endtemplate}
class _FlipperAnchorRevoluteJointDef extends RevoluteJointDef { class _FlipperAnchorRevoluteJointDef extends RevoluteJointDef {

@ -12,7 +12,7 @@ import 'package:pinball_components/pinball_components.dart';
/// initialize( /// initialize(
/// dynamicBody.body, /// dynamicBody.body,
/// anchor.body, /// anchor.body,
/// dynabmicBody.body + anchor.body.position, /// dynamicBody.body + anchor.body.position,
/// ); /// );
/// ``` /// ```
/// {@endtemplate} /// {@endtemplate}

@ -6,7 +6,7 @@ import 'package:pinball_flame/pinball_flame.dart';
/// {@template plunger} /// {@template plunger}
/// [Plunger] serves as a spring, that shoots the ball on the right side of the /// [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]. /// [Plunger] ignores gravity so the player controls its downward [pull].
/// {@endtemplate} /// {@endtemplate}

@ -37,7 +37,7 @@ class Slingshot extends BodyComponent with InitialPosition {
}) : _angle = angle, }) : _angle = angle,
super( super(
children: [ children: [
_SlinghsotSpriteComponent(spritePath, angle: angle), _SlingshotSpriteComponent(spritePath, angle: angle),
BumpingBehavior(strength: 20), BumpingBehavior(strength: 20),
], ],
renderBody: false, renderBody: false,
@ -90,8 +90,8 @@ class Slingshot extends BodyComponent with InitialPosition {
} }
} }
class _SlinghsotSpriteComponent extends SpriteComponent with HasGameRef { class _SlingshotSpriteComponent extends SpriteComponent with HasGameRef {
_SlinghsotSpriteComponent( _SlingshotSpriteComponent(
String path, { String path, {
required double angle, required double angle,
}) : _path = path, }) : _path = path,

@ -1,4 +1,4 @@
# Sandbox # Pinball Components Sandbox
![coverage][coverage_badge] ![coverage][coverage_badge]
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] [![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] 🤖 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
--- ---

@ -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();
}
}
}

@ -1,16 +1,9 @@
import 'package:dashbook/dashbook.dart'; import 'package:dashbook/dashbook.dart';
import 'package:sandbox/common/common.dart'; import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/effects/camera_zoom_game.dart'; import 'package:sandbox/stories/effects/camera_zoom_game.dart';
import 'package:sandbox/stories/effects/fire_effect_game.dart';
void addEffectsStories(Dashbook dashbook) { void addEffectsStories(Dashbook dashbook) {
dashbook.storiesOf('Effects') dashbook.storiesOf('Effects').addGame(
..addGame(
title: 'Fire',
description: FireEffectGame.description,
gameBuilder: (_) => FireEffectGame(),
)
..addGame(
title: 'CameraZoom', title: 'CameraZoom',
description: CameraZoomGame.description, description: CameraZoomGame.description,
gameBuilder: (_) => CameraZoomGame(), gameBuilder: (_) => CameraZoomGame(),

@ -32,7 +32,7 @@ void main() {
}); });
flameTester.testGameWidget( flameTester.testGameWidget(
'the bump is greater when the strengh is greater', 'the bump is greater when the strength is greater',
setUp: (game, tester) async { setUp: (game, tester) async {
final component1 = _TestBodyComponent(); final component1 = _TestBodyComponent();
final behavior1 = BumpingBehavior(strength: 1) final behavior1 = BumpingBehavior(strength: 1)

@ -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);
},
);
}

@ -67,7 +67,7 @@ void main() {
}, },
); );
flameTester.test('adds a FlapperSpiningBehavior to FlapperEntrance', flameTester.test('adds a FlapperSpinningBehavior to FlapperEntrance',
(game) async { (game) async {
final flapper = Flapper(); final flapper = Flapper();
await game.ensureAdd(flapper); await game.ensureAdd(flapper);

@ -54,7 +54,7 @@ void main() {
); );
flameTester.test( flameTester.test(
'deafaults to zero ' 'defaults to zero '
'when no initialPosition is given', 'when no initialPosition is given',
(game) async { (game) async {
final component = TestBodyComponent(); final component = TestBodyComponent();

@ -329,7 +329,7 @@ void main() {
); );
flameTester.test( flameTester.test(
'connected body collison enabled', 'connected body collision enabled',
(game) async { (game) async {
await game.ensureAdd(plunger); await game.ensureAdd(plunger);
await game.ensureAdd(anchor); await game.ensureAdd(anchor);

@ -12,7 +12,7 @@ typedef PaintFunction = void Function(Paint);
/// {@template canvas_component} /// {@template canvas_component}
/// Allows listening before the rendering of [Sprite]s. /// 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 /// provide a global way to modify the default [Paint] before rendering a
/// [Sprite]. /// [Sprite].
/// {@endtemplate} /// {@endtemplate}

@ -4,7 +4,7 @@ import 'package:flame/game.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_forge2d/world_contact_listener.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} /// {@template pinball_forge2d_game}
/// A [Game] that uses the Forge2D physics engine. /// A [Game] that uses the Forge2D physics engine.
/// {@endtemplate} /// {@endtemplate}

@ -50,7 +50,7 @@ void main() {
); );
flameTester.testGameWidget( flameTester.testGameWidget(
'calls onSpritePainted when paiting a sprite', 'calls onSpritePainted when painting a sprite',
setUp: (game, tester) async { setUp: (game, tester) async {
final spriteComponent = _TestSpriteComponent(); final spriteComponent = _TestSpriteComponent();

@ -38,9 +38,9 @@ void main() {
final component = Component(); final component = Component();
final controller = TestComponentController(component); final controller = TestComponentController(component);
final anotherComponet = Component(); final anotherComponent = Component();
await expectLater( await expectLater(
() async => await anotherComponet.add(controller), () async => await anotherComponent.add(controller),
throwsAssertionError, throwsAssertionError,
); );
}, },

@ -19,7 +19,7 @@ void main() {
}); });
flameTester.test( flameTester.test(
'screenToFlameWorld throws UnimpelementedError', 'screenToFlameWorld throws UnimplementedError',
(game) async { (game) async {
expect( expect(
() => game.screenToFlameWorld(Vector2.zero()), () => game.screenToFlameWorld(Vector2.zero()),
@ -29,7 +29,7 @@ void main() {
); );
flameTester.test( flameTester.test(
'screenToWorld throws UnimpelementedError', 'screenToWorld throws UnimplementedError',
(game) async { (game) async {
expect( expect(
() => game.screenToWorld(Vector2.zero()), () => game.screenToWorld(Vector2.zero()),
@ -39,7 +39,7 @@ void main() {
); );
flameTester.test( flameTester.test(
'worldToScreen throws UnimpelementedError', 'worldToScreen throws UnimplementedError',
(game) async { (game) async {
expect( expect(
() => game.worldToScreen(Vector2.zero()), () => game.worldToScreen(Vector2.zero()),

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; 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 /// 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 /// [text]. Once 3 `.` have been added (e.g. `Loading...`), it will reset to
/// zero ellipsis and start over again. /// zero ellipsis and start over again.

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
/// {@template crt_background} /// {@template crt_background}
/// [BoxDecoration] that provides a CRT-like background efffect. /// [BoxDecoration] that provides a CRT-like background effect.
/// {@endtemplate} /// {@endtemplate}
class CrtBackground extends BoxDecoration { class CrtBackground extends BoxDecoration {
/// {@macro crt_background} /// {@macro crt_background}

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:pinball_ui/gen/gen.dart'; import 'package:pinball_ui/gen/gen.dart';
import 'package:pinball_ui/pinball_ui.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 { enum PinballDpadDirection {
/// Up /// Up
up, up,

@ -27,7 +27,7 @@ void main() {
}); });
test( test(
'returns false when defaultTargetPlatform is niether iOS nor android', 'returns false when defaultTargetPlatform is neither iOS nor android',
() async { () async {
debugDefaultTargetPlatformOverride = TargetPlatform.macOS; debugDefaultTargetPlatformOverride = TargetPlatform.macOS;
expect(PlatformHelper().isMobile, isFalse); expect(PlatformHelper().isMobile, isFalse);

@ -11,6 +11,12 @@ class ShareRepository {
final String _appUrl; 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]. /// 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. /// The returned url can be opened using the [url_launcher](https://pub.dev/packages/url_launcher) package.

@ -576,6 +576,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
share_repository:
dependency: "direct main"
description:
path: "packages/share_repository"
relative: true
source: path
version: "1.0.0+1"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@ -694,7 +701,7 @@ packages:
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
url_launcher: url_launcher:
dependency: transitive dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"

@ -44,6 +44,9 @@ dependencies:
path: packages/pinball_ui path: packages/pinball_ui
platform_helper: platform_helper:
path: packages/platform_helper path: packages/platform_helper
share_repository:
path: packages/share_repository
url_launcher: ^6.1.0
dev_dependencies: dev_dependencies:
bloc_test: ^9.0.2 bloc_test: ^9.0.2

@ -279,21 +279,5 @@ void main() {
); );
}, },
); );
group('SparkyTurboChargeActivated', () {
blocTest<GameBloc, GameState>(
'adds game bonus',
build: GameBloc.new,
act: (bloc) => bloc..add(const SparkyTurboChargeActivated()),
expect: () => [
isA<GameState>()
..having(
(state) => state.bonusHistory,
'bonusHistory',
[GameBonus.sparkyTurboCharge],
),
],
);
});
}); });
} }

@ -97,17 +97,4 @@ void main() {
); );
}); });
}); });
group('SparkyTurboChargeActivated', () {
test('can be instantiated', () {
expect(const SparkyTurboChargeActivated(), isNotNull);
});
test('supports value equality', () {
expect(
SparkyTurboChargeActivated(),
equals(SparkyTurboChargeActivated()),
);
});
});
} }

@ -3,6 +3,7 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.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:pinball_theme/pinball_theme.dart' as theme;
import 'package:platform_helper/platform_helper.dart'; import 'package:platform_helper/platform_helper.dart';
class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents { class _TestGame extends Forge2DGame
with HasKeyboardHandlerComponents, HasTappables {
final character = theme.DashTheme(); final character = theme.DashTheme();
@override @override
@ -34,6 +36,7 @@ class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents {
character.leaderboardIcon.keyName, character.leaderboardIcon.keyName,
Assets.images.backbox.marquee.keyName, Assets.images.backbox.marquee.keyName,
Assets.images.backbox.displayDivider.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 _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
} }
class _MockTapDownInfo extends Mock implements TapDownInfo {}
class _MockAppLocalizations extends Mock implements AppLocalizations { class _MockAppLocalizations extends Mock implements AppLocalizations {
@override @override
String get score => ''; String get score => '';
@ -100,6 +105,33 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
@override @override
String get loading => ''; 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 @override
String get leaderboardErrorMessage => ''; 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<InitialsSuccessState>.empty(),
initialState: state,
);
final backbox = Backbox.test(
bloc: bloc,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
game.descendants().whereType<GameOverInfoDisplay>().length,
equals(1),
);
},
);
flameTester.test( flameTester.test(
'adds the mobile controls overlay when platform is mobile', 'adds the mobile controls overlay when platform is mobile',
(game) async { (game) async {
@ -246,10 +300,11 @@ void main() {
flameTester.test( flameTester.test(
'adds InitialsSubmissionSuccessDisplay on InitialsSuccessState', 'adds InitialsSubmissionSuccessDisplay on InitialsSuccessState',
(game) async { (game) async {
final state = InitialsSuccessState(score: 100);
whenListen( whenListen(
bloc, bloc,
Stream<BackboxState>.empty(), const Stream<InitialsSuccessState>.empty(),
initialState: InitialsSuccessState(), initialState: state,
); );
final backbox = Backbox.test( final backbox = Backbox.test(
bloc: bloc, bloc: bloc,
@ -258,22 +313,49 @@ void main() {
await game.pump(backbox); await game.pump(backbox);
expect( expect(
game game.descendants().whereType<GameOverInfoDisplay>().length,
.descendants()
.whereType<InitialsSubmissionSuccessDisplay>()
.length,
equals(1), 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<ShareLinkComponent>().first;
shareLink.onTapDown(_MockTapDownInfo());
verify(
() => bloc.add(
ShareScoreRequested(score: state.score),
),
).called(1);
},
);
flameTester.test( flameTester.test(
'adds InitialsSubmissionFailureDisplay on InitialsFailureState', 'adds InitialsSubmissionFailureDisplay on InitialsFailureState',
(game) async { (game) async {
whenListen( whenListen(
bloc, bloc,
Stream<BackboxState>.empty(), Stream<BackboxState>.empty(),
initialState: InitialsFailureState(), initialState: InitialsFailureState(
score: 0,
character: theme.DashTheme(),
),
); );
final backbox = Backbox.test( final backbox = Backbox.test(
bloc: bloc, bloc: bloc,
@ -354,7 +436,12 @@ void main() {
backbox.removeFromParent(); backbox.removeFromParent();
await game.ready(); await game.ready();
streamController.add(InitialsFailureState()); streamController.add(
InitialsFailureState(
score: 10,
character: theme.DashTheme(),
),
);
await game.ready(); await game.ready();
expect( 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<BackboxState>.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);
},
);
}); });
} }

@ -82,7 +82,7 @@ void main() {
), ),
expect: () => [ expect: () => [
LoadingState(), LoadingState(),
InitialsSuccessState(), InitialsSuccessState(score: 10),
], ],
); );
@ -113,7 +113,26 @@ void main() {
), ),
expect: () => [ expect: () => [
LoadingState(), LoadingState(),
InitialsFailureState(), InitialsFailureState(score: 10, character: DashTheme()),
],
);
});
group('ShareScoreRequested', () {
blocTest<BackboxBloc, BackboxState>(
'emits ShareState',
setUp: () {
leaderboardRepository = _MockLeaderboardRepository();
},
build: () => BackboxBloc(
leaderboardRepository: leaderboardRepository,
initialEntries: emptyEntries,
),
act: (bloc) => bloc.add(
ShareScoreRequested(score: 100),
),
expect: () => [
ShareState(score: 100),
], ],
); );
}); });

@ -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', () { group('LeaderboardRequested', () {
test('can be instantiated', () { test('can be instantiated', () {
expect(LeaderboardRequested(), isNotNull); expect(LeaderboardRequested(), isNotNull);

@ -115,20 +115,91 @@ void main() {
group('InitialsSuccessState', () { group('InitialsSuccessState', () {
test('can be instantiated', () { test('can be instantiated', () {
expect(InitialsSuccessState(), isNotNull); expect(
InitialsSuccessState(score: 0),
isNotNull,
);
}); });
test('supports value comparison', () { test('supports value comparison', () {
expect(InitialsSuccessState(), equals(InitialsSuccessState())); expect(
InitialsSuccessState(score: 0),
equals(
InitialsSuccessState(score: 0),
),
);
}); });
group('InitialsFailureState', () { group('InitialsFailureState', () {
test('can be instantiated', () { test('can be instantiated', () {
expect(InitialsFailureState(), isNotNull); expect(
InitialsFailureState(
score: 10,
character: AndroidTheme(),
),
isNotNull,
);
}); });
test('supports value comparison', () { test('supports value comparison', () {
expect(InitialsFailureState(), equals(InitialsFailureState())); 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(
ShareState(score: 0),
equals(
ShareState(score: 0),
),
);
}); });
}); });
}); });

@ -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<void> onLoad() async {
await super.onLoad();
images.prefix = '';
await images.loadAll(
[
Assets.images.backbox.displayTitleDecoration.keyName,
],
);
}
Future<void> pump(GameOverInfoDisplay component) {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.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<ShareLinkComponent>().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<GoogleIOLinkComponent>().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<OpenSourceTextComponent>().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'),
),
);
},
);
});
}

@ -129,7 +129,7 @@ void main() {
}, },
); );
String? submitedInitials; String? submittedInitials;
flameTester.testGameWidget( flameTester.testGameWidget(
'submits the initials', 'submits the initials',
setUp: (game, tester) async { setUp: (game, tester) async {
@ -138,7 +138,7 @@ void main() {
score: 1000, score: 1000,
characterIconPath: game.characterIconPath, characterIconPath: game.characterIconPath,
onSubmit: (value) { onSubmit: (value) {
submitedInitials = value; submittedInitials = value;
}, },
); );
await game.pump(component); await game.pump(component);
@ -147,7 +147,7 @@ void main() {
await tester.pump(); await tester.pump();
}, },
verify: (game, tester) async { verify: (game, tester) async {
expect(submitedInitials, equals('AAA')); expect(submittedInitials, equals('AAA'));
}, },
); );

@ -4,18 +4,74 @@ import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_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/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<void> onLoad() async {
await super.onLoad();
images.prefix = '';
await images.loadAll(
[
Assets.images.errorBackground.keyName,
],
);
}
Future<void> 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() { void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('InitialsSubmissionFailureDisplay', () { group('InitialsSubmissionFailureDisplay', () {
final flameTester = FlameTester(Forge2DGame.new); final flameTester = FlameTester(_TestGame.new);
flameTester.test('renders correctly', (game) async { flameTester.test('renders correctly', (game) async {
await game.ensureAdd(InitialsSubmissionFailureDisplay()); await game.pump(
InitialsSubmissionFailureDisplay(
onDismissed: () {},
),
);
final component = game.firstChild<TextComponent>(); expect(
expect(component, isNotNull); game
expect(component?.text, equals('Failure!')); .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),
);
}); });
}); });
} }

@ -95,9 +95,9 @@ void main() {
), ),
); );
final basebottomGroups = final baseBottomGroups =
bottomGroup.descendants().whereType<Baseboard>(); bottomGroup.descendants().whereType<Baseboard>();
expect(basebottomGroups.length, equals(2)); expect(baseBottomGroups.length, equals(2));
}, },
); );

@ -162,7 +162,7 @@ void main() {
); );
flameTester.test( flameTester.test(
'removes FlipperKeyControllingBehavior from Fipper', 'removes FlipperKeyControllingBehavior from Flipper',
(game) async { (game) async {
final component = GameBlocStatusListener(); final component = GameBlocStatusListener();
final repository = _MockLeaderboardRepository(); final repository = _MockLeaderboardRepository();
@ -242,7 +242,7 @@ void main() {
); );
flameTester.test( 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 { (game) async {
final component = GameBlocStatusListener(); final component = GameBlocStatusListener();
final repository = _MockLeaderboardRepository(); final repository = _MockLeaderboardRepository();

@ -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);
});
});
}

@ -14,7 +14,7 @@ void main() {
}); });
blocTest<CharacterThemeCubit, CharacterThemeState>( blocTest<CharacterThemeCubit, CharacterThemeState>(
'charcterSelected emits selected character theme', 'characterSelected emits selected character theme',
build: CharacterThemeCubit.new, build: CharacterThemeCubit.new,
act: (bloc) => bloc.characterSelected(const SparkyTheme()), act: (bloc) => bloc.characterSelected(const SparkyTheme()),
expect: () => [ expect: () => [

@ -15,6 +15,17 @@ void main() {
], ],
); );
blocTest<StartGameBloc, StartGameState>(
'on ReplayTapped changes status to selectCharacter',
build: StartGameBloc.new,
act: (bloc) => bloc.add(const ReplayTapped()),
expect: () => [
const StartGameState(
status: StartGameStatus.selectCharacter,
)
],
);
blocTest<StartGameBloc, StartGameState>( blocTest<StartGameBloc, StartGameState>(
'on CharacterSelected changes status to howToPlay', 'on CharacterSelected changes status to howToPlay',
build: StartGameBloc.new, build: StartGameBloc.new,

@ -12,6 +12,13 @@ void main() {
); );
}); });
test('ReplayTapped supports value equality', () {
expect(
ReplayTapped(),
equals(ReplayTapped()),
);
});
test('CharacterSelected supports value equality', () { test('CharacterSelected supports value equality', () {
expect( expect(
CharacterSelected(), CharacterSelected(),

@ -25,10 +25,41 @@ void main() {
expect(testState, secondState); expect(testState, secondState);
}); });
test('supports copyWith', () { group('copyWith', () {
final secondState = testState.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', () { test('has correct props', () {

@ -46,12 +46,14 @@ void main() {
}); });
testWidgets( testWidgets(
'calls onGameStarted event', 'calls GameStarted event',
(tester) async { (tester) async {
whenListen( whenListen(
startGameBloc, startGameBloc,
Stream.value( Stream.value(
const StartGameState(status: StartGameStatus.selectCharacter), const StartGameState(
status: StartGameStatus.selectCharacter,
),
), ),
initialState: const StartGameState.initial(), initialState: const StartGameState.initial(),
); );

Loading…
Cancel
Save