feat: `GameOverInfoDisplay` on game over `Backbox` (#359)

* feat: info screen ui

* feat: info screen ui

* feat: replay button

* feat: added replay button and game flow

* feat: game over info display flow at backbox

* test: fixed tests and coverage

* test: fix tests and coverage

* chore: info display doc"

* refactor: text component name

* Update lib/game/components/backbox/bloc/backbox_state.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update lib/game/components/backbox/bloc/backbox_state.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update lib/game/components/backbox/bloc/backbox_state.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/game/components/backbox/displays/info_display_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/start_game/widgets/start_game_listener_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/start_game/widgets/start_game_listener_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/game/view/widgets/replay_button_overlay_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/game/components/backbox/displays/info_display_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update lib/game/components/backbox/displays/info_display.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update lib/game/components/backbox/displays/info_display.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update lib/game/components/backbox/displays/info_display.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update lib/game/components/backbox/displays/info_display.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update lib/game/view/widgets/replay_button_overlay.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update lib/l10n/arb/app_en.arb

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/game/components/backbox/backbox_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/game/components/backbox/bloc/backbox_bloc_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update lib/l10n/arb/app_en.arb

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* refactor: changed names and removed replaying

* refactor: removed restart logic changes for another pr

* feat: link underscore

* chore: test fixed, names changed

* chore: unused import

* feat: add replayButtonOverlay at GameOverInfoDisplay

* feat: added open source url url launcher

* feat: launch url and test

* chore: removed comments

* fix: remove overlay for Replay to merge info screen

* chore: unused import

* test: load images bug fix

* Update lib/game/components/backbox/displays/game_over_info_display.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* refactor: removed props from state for sharing only score

* feat: moved open links to info display and added link to OS

* test: fixed merge tests

* chore: unused imports and annotation missed

* Update lib/game/components/backbox/bloc/backbox_state.dart

* chore: tappables added to cspell

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
Co-authored-by: Tom Arra <tarra3@gmail.com>
refactor/blinking-behavior
Rui Miguel Alonso 2 years ago committed by GitHub
parent 5ebddd37c2
commit fdb9075738
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -35,6 +35,8 @@
"rrect",
"serializable",
"sparky's",
"tappable",
"tappables",
"texto",
"theming",
"unawaited",

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

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

@ -52,6 +52,22 @@ class PlayerInitialsSubmitted extends BackboxEvent {
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
class LeaderboardRequested extends BackboxEvent {
@override

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

@ -101,6 +101,9 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.sparky.bumper.c.dimmed.keyName),
images.load(components.Assets.images.backbox.marquee.keyName),
images.load(components.Assets.images.backbox.displayDivider.keyName),
images.load(
components.Assets.images.backbox.displayTitleDecoration.keyName,
),
images.load(components.Assets.images.googleWord.letter1.lit.keyName),
images.load(components.Assets.images.googleWord.letter1.dimmed.keyName),
images.load(components.Assets.images.googleWord.letter2.lit.keyName),

@ -16,7 +16,7 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
class PinballGame extends PinballForge2DGame
with HasKeyboardHandlerComponents, MultiTouchTapDetector {
with HasKeyboardHandlerComponents, MultiTouchTapDetector, HasTappables {
PinballGame({
required CharacterThemeCubit characterThemeBloc,
required this.leaderboardRepository,

@ -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_dpad.dart';
export 'play_button_overlay.dart';
export 'replay_button_overlay.dart';
export 'round_count_display.dart';
export 'score_view.dart';

@ -148,6 +148,42 @@
"@loading": {
"description": "Text shown to indicate loading times"
},
"replay": "Replay",
"@replay": {
"description": "Text displayed on the share page for replay button"
},
"ioPinball": "I/O Pinball",
"@ioPinball": {
"description": "I/O Pinball - Name of the game"
},
"shareYourScore": "Share your score",
"@shareYourScore": {
"description": "Text shown on title of info screen"
},
"andChallengeYourFriends": "AND CHALLENGE YOUR FRIENDS",
"@challengeYourFriends": {
"description": "Text shown on title of info screen"
},
"share": "SHARE",
"@share": {
"description": "Text for share link on info screen."
},
"gotoIO": "GO TO I/O",
"@gotoIO": {
"description": "Text for going to I/O site link on info screen."
},
"learnMore": "Learn more about building games in Flutter with",
"@learnMore": {
"description": "Text shown on description of info screen"
},
"firebaseOr": "Firebase or dive right into the",
"@firebaseOr": {
"description": "Text shown on description of info screen"
},
"openSourceCode": "open source code.",
"@openSourceCode": {
"description": "Text shown on description of info screen"
},
"enter": "Enter",
"@enter": {
"description": "Text shown on the mobile controls enter button"

@ -11,6 +11,7 @@ class StartGameBloc extends Bloc<StartGameEvent, StartGameState> {
/// {@macro start_game_bloc}
StartGameBloc() : super(const StartGameState.initial()) {
on<PlayTapped>(_onPlayTapped);
on<ReplayTapped>(_onReplayTapped);
on<CharacterSelected>(_onCharacterSelected);
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(
CharacterSelected event,
Emitter<StartGameState> emit,

@ -19,6 +19,17 @@ class PlayTapped extends StartGameEvent {
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}
/// Character selected event.
/// {@endtemplate}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

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

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

@ -11,6 +11,12 @@ class ShareRepository {
final String _appUrl;
/// Url to the Github Open Source Pinball project.
static const openSourceCode = 'https://github.com/VGVentures/pinball';
/// Url to the Google IO Event.
static const googleIOEvent = 'https://events.google.com/io/';
/// Returns a url to share the [value] on the given [platform].
///
/// The returned url can be opened using the [url_launcher](https://pub.dev/packages/url_launcher) package.

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

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

@ -3,6 +3,7 @@
import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
@ -21,7 +22,8 @@ import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
import 'package:platform_helper/platform_helper.dart';
class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents {
class _TestGame extends Forge2DGame
with HasKeyboardHandlerComponents, HasTappables {
final character = theme.DashTheme();
@override
@ -34,6 +36,7 @@ class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents {
character.leaderboardIcon.keyName,
Assets.images.backbox.marquee.keyName,
Assets.images.backbox.displayDivider.keyName,
Assets.images.backbox.displayTitleDecoration.keyName,
]);
}
@ -72,6 +75,8 @@ class _MockBackboxBloc extends Mock implements BackboxBloc {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
class _MockTapDownInfo extends Mock implements TapDownInfo {}
class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get score => '';
@ -100,6 +105,27 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get loading => '';
@override
String get shareYourScore => '';
@override
String get andChallengeYourFriends => '';
@override
String get share => '';
@override
String get gotoIO => '';
@override
String get learnMore => '';
@override
String get firebaseOr => '';
@override
String get openSourceCode => '';
@override
String get initialsErrorTitle => '';
@ -221,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(
'adds the mobile controls overlay when platform is mobile',
(game) async {
@ -252,10 +300,11 @@ void main() {
flameTester.test(
'adds InitialsSubmissionSuccessDisplay on InitialsSuccessState',
(game) async {
final state = InitialsSuccessState(score: 100);
whenListen(
bloc,
Stream<BackboxState>.empty(),
initialState: InitialsSuccessState(),
const Stream<InitialsSuccessState>.empty(),
initialState: state,
);
final backbox = Backbox.test(
bloc: bloc,
@ -264,15 +313,39 @@ void main() {
await game.pump(backbox);
expect(
game
.descendants()
.whereType<InitialsSubmissionSuccessDisplay>()
.length,
game.descendants().whereType<GameOverInfoDisplay>().length,
equals(1),
);
},
);
flameTester.test(
'adds ShareScoreRequested event when sharing',
(game) async {
final state = InitialsSuccessState(score: 100);
whenListen(
bloc,
Stream.value(state),
initialState: state,
);
final backbox = Backbox.test(
bloc: bloc,
platformHelper: platformHelper,
);
await game.pump(backbox);
final shareLink =
game.descendants().whereType<ShareLinkComponent>().first;
shareLink.onTapDown(_MockTapDownInfo());
verify(
() => bloc.add(
ShareScoreRequested(score: state.score),
),
).called(1);
},
);
flameTester.test(
'adds InitialsSubmissionFailureDisplay on InitialsFailureState',
(game) async {

@ -82,7 +82,7 @@ void main() {
),
expect: () => [
LoadingState(),
InitialsSuccessState(),
InitialsSuccessState(score: 10),
],
);
@ -118,6 +118,25 @@ void main() {
);
});
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),
],
);
});
group('LeaderboardRequested', () {
blocTest<BackboxBloc, BackboxState>(
'adds [LoadingState, LeaderboardSuccessState] when request succeeds',

@ -123,6 +123,33 @@ void main() {
});
});
group('ScoreShareRequested', () {
test('can be instantiated', () {
expect(
ShareScoreRequested(score: 0),
isNotNull,
);
});
test('supports value comparison', () {
expect(
ShareScoreRequested(score: 0),
equals(
ShareScoreRequested(score: 0),
),
);
expect(
ShareScoreRequested(score: 0),
isNot(
equals(
ShareScoreRequested(score: 1),
),
),
);
});
});
group('LeaderboardRequested', () {
test('can be instantiated', () {
expect(LeaderboardRequested(), isNotNull);

@ -115,11 +115,19 @@ void main() {
group('InitialsSuccessState', () {
test('can be instantiated', () {
expect(InitialsSuccessState(), isNotNull);
expect(
InitialsSuccessState(score: 0),
isNotNull,
);
});
test('supports value comparison', () {
expect(InitialsSuccessState(), equals(InitialsSuccessState()));
expect(
InitialsSuccessState(score: 0),
equals(
InitialsSuccessState(score: 0),
),
);
});
group('InitialsFailureState', () {
@ -176,6 +184,24 @@ void main() {
);
});
});
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'),
),
);
},
);
});
}

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

@ -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>(
'on CharacterSelected changes status to howToPlay',
build: StartGameBloc.new,

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

@ -25,10 +25,41 @@ void main() {
expect(testState, secondState);
});
test('supports copyWith', () {
final secondState = testState.copyWith();
group('copyWith', () {
test(
'copies correctly '
'when no argument specified',
() {
const state = StartGameState(
status: StartGameStatus.initial,
);
expect(
state.copyWith(),
equals(state),
);
},
);
expect(testState, secondState);
test(
'copies correctly '
'when all arguments specified',
() {
const state = StartGameState(
status: StartGameStatus.initial,
);
final otherState = StartGameState(
status: StartGameStatus.play,
);
expect(state, isNot(equals(otherState)));
expect(
state.copyWith(
status: otherState.status,
),
equals(otherState),
);
},
);
});
test('has correct props', () {

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

Loading…
Cancel
Save