fix: merge conflicts with main

pull/406/head
RuiAlonso 3 years ago
commit deee2119fc

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

@ -34,8 +34,11 @@ class Backbox extends PositionComponent with ZIndex {
anchor = Anchor.bottomCenter;
zIndex = ZIndexes.backbox;
_bloc.add(LeaderboardRequested());
await add(_BackboxSpriteComponent());
await add(_display = Component());
_build(_bloc.state);
_subscription = _bloc.stream.listen((state) {
_display.children.removeWhere((_) => true);
@ -52,6 +55,8 @@ class Backbox extends PositionComponent with ZIndex {
void _build(BackboxState state) {
if (state is LoadingState) {
_display.add(LoadingDisplay());
} else if (state is LeaderboardSuccessState) {
_display.add(LeaderboardDisplay(entries: state.entries));
} else if (state is InitialsFormState) {
_display.add(
InitialsInputDisplay(

@ -18,6 +18,7 @@ class BackboxBloc extends Bloc<BackboxEvent, BackboxState> {
super(LoadingState()) {
on<PlayerInitialsRequested>(_onPlayerInitialsRequested);
on<PlayerInitialsSubmitted>(_onPlayerInitialsSubmitted);
on<LeaderboardRequested>(_onLeaderboardRequested);
}
final LeaderboardRepository _leaderboardRepository;
@ -53,4 +54,20 @@ class BackboxBloc extends Bloc<BackboxEvent, BackboxState> {
emit(InitialsFailureState());
}
}
Future<void> _onLeaderboardRequested(
LeaderboardRequested event,
Emitter<BackboxState> emit,
) async {
try {
emit(LoadingState());
final entries = await _leaderboardRepository.fetchTop10Leaderboard();
emit(LeaderboardSuccessState(entries: entries));
} catch (error, stackTrace) {
addError(error, stackTrace);
emit(LeaderboardFailureState());
}
}
}

@ -51,3 +51,9 @@ class PlayerInitialsSubmitted extends BackboxEvent {
@override
List<Object?> get props => [score, initials, character];
}
/// Event that triggers the fetching of the leaderboard
class LeaderboardRequested extends BackboxEvent {
@override
List<Object?> get props => [];
}

@ -14,10 +14,18 @@ class LoadingState extends BackboxState {
List<Object?> get props => [];
}
/// {@template leaderboard_success_state}
/// State when the leaderboard was successfully loaded.
/// {@endtemplate}
class LeaderboardSuccessState extends BackboxState {
/// {@macro leaderboard_success_state}
const LeaderboardSuccessState({required this.entries});
/// Current entries
final List<LeaderboardEntryData> entries;
@override
List<Object?> get props => [];
List<Object?> get props => [entries];
}
/// State when the leaderboard failed to load.

@ -1,5 +1,6 @@
export 'initials_input_display.dart';
export 'initials_submission_failure_display.dart';
export 'initials_submission_success_display.dart';
export 'leaderboard_display.dart';
export 'loading_display.dart';
export 'share_display.dart';

@ -0,0 +1,120 @@
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/leaderboard/models/leader_board_entry.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_ui/pinball_ui.dart';
final _titleTextPaint = TextPaint(
style: const TextStyle(
fontSize: 2,
color: PinballColors.red,
fontFamily: PinballFonts.pixeloidSans,
),
);
final _bodyTextPaint = TextPaint(
style: const TextStyle(
fontSize: 1.8,
color: PinballColors.white,
fontFamily: PinballFonts.pixeloidSans,
),
);
/// {@template leaderboard_display}
/// Component that builds the leaderboard list of the Backbox.
/// {@endtemplate}
class LeaderboardDisplay extends PositionComponent with HasGameRef {
/// {@macro leaderboard_display}
LeaderboardDisplay({required List<LeaderboardEntryData> entries})
: _entries = entries;
final List<LeaderboardEntryData> _entries;
double _calcY(int i) => (i * 3.2) + 3.2;
static const _columns = [-15.0, 0.0, 15.0];
String _rank(int number) {
switch (number) {
case 1:
return '${number}st';
case 2:
return '${number}nd';
case 3:
return '${number}rd';
default:
return '${number}th';
}
}
@override
Future<void> onLoad() async {
position = Vector2(0, -30);
final l10n = readProvider<AppLocalizations>();
final ranking = _entries.take(5).toList();
await add(
PositionComponent(
position: Vector2(0, 4),
children: [
PositionComponent(
children: [
TextComponent(
text: l10n.rank,
textRenderer: _titleTextPaint,
position: Vector2(_columns[0], 0),
anchor: Anchor.center,
),
TextComponent(
text: l10n.score,
textRenderer: _titleTextPaint,
position: Vector2(_columns[1], 0),
anchor: Anchor.center,
),
TextComponent(
text: l10n.name,
textRenderer: _titleTextPaint,
position: Vector2(_columns[2], 0),
anchor: Anchor.center,
),
],
),
for (var i = 0; i < ranking.length; i++)
PositionComponent(
children: [
TextComponent(
text: _rank(i + 1),
textRenderer: _bodyTextPaint,
position: Vector2(_columns[0], _calcY(i)),
anchor: Anchor.center,
),
TextComponent(
text: ranking[i].score.formatScore(),
textRenderer: _bodyTextPaint,
position: Vector2(_columns[1], _calcY(i)),
anchor: Anchor.center,
),
SpriteComponent.fromImage(
gameRef.images.fromCache(
ranking[i].character.toTheme.leaderboardIcon.keyName,
),
anchor: Anchor.center,
size: Vector2(1.8, 1.8),
position: Vector2(_columns[2] - 2.5, _calcY(i) + .25),
),
TextComponent(
text: ranking[i].playerInitials,
textRenderer: _bodyTextPaint,
position: Vector2(_columns[2] + 1, _calcY(i)),
anchor: Anchor.center,
),
],
),
],
),
);
}
}

@ -24,7 +24,8 @@ class PinballGame extends PinballForge2DGame
required GameBloc gameBloc,
required AppLocalizations l10n,
required PinballPlayer player,
}) : _gameBloc = gameBloc,
}) : focusNode = FocusNode(),
_gameBloc = gameBloc,
_player = player,
_characterTheme = characterTheme,
_l10n = l10n,
@ -40,6 +41,8 @@ class PinballGame extends PinballForge2DGame
@override
Color backgroundColor() => Colors.transparent;
final FocusNode focusNode;
final CharacterTheme _characterTheme;
final PinballPlayer _player;

@ -114,43 +114,63 @@ class PinballGameLoadedView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isPlaying = context.select(
(StartGameBloc bloc) => bloc.state.status == StartGameStatus.play,
);
final gameWidgetWidth = MediaQuery.of(context).size.height * 9 / 16;
final screenWidth = MediaQuery.of(context).size.width;
final leftMargin = (screenWidth / 2) - (gameWidgetWidth / 1.8);
final clampedMargin = leftMargin > 0 ? leftMargin : 0.0;
return StartGameListener(
child: Stack(
children: [
Positioned.fill(
child: GameWidget<PinballGame>(
game: game,
initialActiveOverlays: const [PinballGame.playButtonOverlay],
overlayBuilderMap: {
PinballGame.playButtonOverlay: (context, game) {
return const Positioned(
bottom: 20,
right: 0,
left: 0,
child: PlayButtonOverlay(),
);
},
child: MouseRegion(
onHover: (_) {
if (!game.focusNode.hasFocus) {
game.focusNode.requestFocus();
}
},
child: GameWidget<PinballGame>(
game: game,
focusNode: game.focusNode,
initialActiveOverlays: const [PinballGame.playButtonOverlay],
overlayBuilderMap: {
PinballGame.playButtonOverlay: (context, game) {
return const Positioned(
bottom: 20,
right: 0,
left: 0,
child: PlayButtonOverlay(),
);
},
},
),
),
),
Positioned(
top: 0,
left: clampedMargin,
child: Visibility(
visible: isPlaying,
child: const GameHud(),
),
),
const _PositionedGameHud(),
],
),
);
}
}
class _PositionedGameHud extends StatelessWidget {
const _PositionedGameHud({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final isPlaying = context.select(
(StartGameBloc bloc) => bloc.state.status == StartGameStatus.play,
);
final isGameOver = context.select(
(GameBloc bloc) => bloc.state.status.isGameOver,
);
final gameWidgetWidth = MediaQuery.of(context).size.height * 9 / 16;
final screenWidth = MediaQuery.of(context).size.width;
final leftMargin = (screenWidth / 2) - (gameWidgetWidth / 1.8);
final clampedMargin = leftMargin > 0 ? leftMargin : 0.0;
return Positioned(
top: 0,
left: clampedMargin,
child: Visibility(
visible: isPlaying && !isGameOver,
child: const GameHud(),
),
);
}
}

@ -14,6 +14,7 @@ class $AssetsImagesGen {
const $AssetsImagesBonusAnimationGen();
$AssetsImagesComponentsGen get components =>
const $AssetsImagesComponentsGen();
$AssetsImagesLinkBoxGen get linkBox => const $AssetsImagesLinkBoxGen();
$AssetsImagesScoreGen get score => const $AssetsImagesScoreGen();
}
@ -53,6 +54,14 @@ class $AssetsImagesComponentsGen {
const AssetGenImage('assets/images/components/space.png');
}
class $AssetsImagesLinkBoxGen {
const $AssetsImagesLinkBoxGen();
/// File path: assets/images/link_box/info_icon.png
AssetGenImage get infoIcon =>
const AssetGenImage('assets/images/link_box/info_icon.png');
}
class $AssetsImagesScoreGen {
const $AssetsImagesScoreGen();

@ -120,6 +120,46 @@
"@footerGoogleIOText": {
"description": "Text shown on the footer which mentions Google I/O"
},
"linkBoxTitle": "Resources",
"@linkBoxTitle": {
"description": "Text shown on the link box title section."
},
"linkBoxMadeWithText": "Made with ",
"@linkBoxMadeWithText": {
"description": "Text shown on the link box which mentions technologies used to build the app."
},
"linkBoxFlutterLinkText": "Flutter",
"@linkBoxFlutterLinkText": {
"description": "Text on the link shown on the link box which navigates to the Flutter page"
},
"linkBoxFirebaseLinkText": "Firebase",
"@linkBoxFirebaseLinkText": {
"description": "Text on the link shown on the link box which navigates to the Firebase page"
},
"linkBoxOpenSourceCode": "Open Source Code",
"@linkBoxOpenSourceCode": {
"description": "Text shown on the link box which redirects to github project"
},
"linkBoxGoogleIOText": "Google I/O",
"@linkBoxGoogleIOText": {
"description": "Text shown on the link box which mentions Google I/O"
},
"linkBoxFlutterGames": "Flutter Games",
"@linkBoxFlutterGames": {
"description": "Text shown on the link box which redirects to flutter games article"
},
"linkBoxHowItsMade": "How its made",
"@linkBoxHowItsMade": {
"description": "Text shown on the link box which redirects to Very Good Blog article"
},
"linkBoxTermsOfService": "Terms of Service",
"@linkBoxTermsOfService": {
"description": "Text shown on the link box which redirect to terms of service"
},
"linkBoxPrivacyPolicy": "Privacy Policy",
"@linkBoxPrivacyPolicy": {
"description": "Text shown on the link box which redirect to privacy policy"
},
"loading": "Loading",
"@loading": {
"description": "Text shown to indicate loading times"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@ -3,8 +3,6 @@
/// FlutterGen
/// *****************************************************
// ignore_for_file: directives_ordering,unnecessary_import
import 'package:flutter/widgets.dart';
class $AssetsImagesGen {
@ -14,14 +12,13 @@ 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();
AssetGenImage get errorBackground =>
const AssetGenImage('assets/images/error_background.png');
$AssetsImagesFlapperGen get flapper => const $AssetsImagesFlapperGen();
$AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen();
$AssetsImagesGoogleWordGen get googleWord =>
@ -54,14 +51,8 @@ class $AssetsImagesAndroidGen {
class $AssetsImagesBackboxGen {
const $AssetsImagesBackboxGen();
$AssetsImagesBackboxButtonGen get button =>
const $AssetsImagesBackboxButtonGen();
/// File path: assets/images/backbox/display-divider.png
AssetGenImage get displayDivider =>
const AssetGenImage('assets/images/backbox/display-divider.png');
/// File path: assets/images/backbox/marquee.png
AssetGenImage get marquee =>
const AssetGenImage('assets/images/backbox/marquee.png');
}
@ -69,7 +60,6 @@ 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');
}
@ -77,11 +67,8 @@ 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');
}
@ -89,15 +76,10 @@ 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');
}
@ -105,10 +87,8 @@ 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();
}
@ -117,16 +97,10 @@ 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');
}
@ -134,15 +108,10 @@ 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');
}
@ -150,11 +119,8 @@ 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');
}
@ -186,15 +152,10 @@ 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');
}
@ -202,11 +163,8 @@ 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');
}
@ -224,11 +182,8 @@ 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');
}
@ -236,19 +191,12 @@ 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');
}
@ -256,19 +204,12 @@ 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');
}
@ -276,19 +217,12 @@ 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');
}
@ -296,11 +230,8 @@ 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');
}
@ -308,10 +239,8 @@ 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 =>
@ -332,11 +261,8 @@ 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');
}
@ -346,20 +272,12 @@ 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');
}
@ -367,15 +285,10 @@ 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');
}
@ -404,11 +317,8 @@ 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');
}
@ -416,11 +326,8 @@ 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');
}
@ -428,11 +335,8 @@ 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');
}
@ -440,11 +344,8 @@ 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');
}
@ -452,11 +353,8 @@ 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');
}
@ -464,11 +362,8 @@ 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');
}
@ -476,11 +371,8 @@ 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');
}
@ -488,11 +380,8 @@ 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');
}
@ -500,11 +389,8 @@ 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');
}
@ -512,11 +398,8 @@ 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');
}
@ -524,11 +407,8 @@ 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');
}
@ -536,11 +416,8 @@ 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');
}
@ -548,11 +425,8 @@ 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');
}
@ -560,11 +434,8 @@ 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');
}
@ -580,15 +451,10 @@ 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');
}
@ -596,11 +462,8 @@ 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');
}
@ -608,11 +471,8 @@ 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');
}
@ -620,11 +480,8 @@ 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');
}
@ -632,27 +489,16 @@ 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');
}
@ -660,11 +506,8 @@ 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');
}
@ -672,11 +515,8 @@ 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');
}
@ -684,11 +524,8 @@ 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');
}
@ -696,11 +533,8 @@ 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');
}
@ -708,11 +542,8 @@ 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');
}
@ -720,11 +551,8 @@ 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,14 +3,9 @@
/// FlutterGen
/// *****************************************************
// ignore_for_file: directives_ordering,unnecessary_import
class FontFamily {
FontFamily._();
/// Font family: PixeloidMono
static const String pixeloidMono = 'PixeloidMono';
/// Font family: PixeloidSans
static const String pixeloidSans = 'PixeloidSans';
}

@ -12,6 +12,7 @@ export 'chrome_dino/chrome_dino.dart';
export 'dash_animatronic.dart';
export 'dash_nest_bumper/dash_nest_bumper.dart';
export 'dino_walls.dart';
export 'error_component.dart';
export 'fire_effect.dart';
export 'flapper/flapper.dart';
export 'flipper.dart';

@ -0,0 +1,95 @@
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_ui/pinball_ui.dart';
final _boldLabelTextPaint = TextPaint(
style: const TextStyle(
fontSize: 1.8,
color: PinballColors.white,
fontFamily: PinballFonts.pixeloidSans,
fontWeight: FontWeight.w700,
),
);
final _labelTextPaint = TextPaint(
style: const TextStyle(
fontSize: 1.8,
color: PinballColors.white,
fontFamily: PinballFonts.pixeloidSans,
fontWeight: FontWeight.w400,
),
);
/// {@template error_component}
/// A plain visual component used to show errors for the user.
/// {@endtemplate}
class ErrorComponent extends SpriteComponent with HasGameRef {
/// {@macro error_component}
ErrorComponent({required this.label, Vector2? position})
: _textPaint = _labelTextPaint,
super(
position: position,
);
/// {@macro error_component}
ErrorComponent.bold({required this.label, Vector2? position})
: _textPaint = _boldLabelTextPaint,
super(
position: position,
);
/// Text shown on the error message.
final String label;
final TextPaint _textPaint;
List<String> _splitInLines() {
final maxWidth = size.x - 8;
final lines = <String>[];
var currentLine = '';
final words = label.split(' ');
while (words.isNotEmpty) {
final word = words.removeAt(0);
if (_textPaint.measureTextWidth('$currentLine $word') <= maxWidth) {
currentLine = '$currentLine $word'.trim();
} else {
lines.add(currentLine);
currentLine = word;
}
}
lines.add(currentLine);
return lines;
}
@override
Future<void> onLoad() async {
anchor = Anchor.center;
final sprite = await gameRef.loadSprite(
Assets.images.errorBackground.keyName,
);
size = sprite.originalSize / 20;
this.sprite = sprite;
final lines = _splitInLines();
// Calculates vertical offset based on the number of lines of text to be
// displayed. This offset is used to keep the middle of the multi-line text
// at the center of the [ErrorComponent].
final yOffset = ((size.y / 2.2) / lines.length) * 1.5;
for (var i = 0; i < lines.length; i++) {
await add(
TextComponent(
position: Vector2(size.x / 2, yOffset + 2.2 * i),
size: Vector2(size.x - 4, 2.2),
text: lines[i],
textRenderer: _textPaint,
anchor: Anchor.center,
),
);
}
}
}

@ -23,6 +23,8 @@ dependencies:
path: ../pinball_flame
pinball_theme:
path: ../pinball_theme
pinball_ui:
path: ../pinball_ui
dev_dependencies:
bloc_test: ^9.0.3
@ -33,6 +35,7 @@ dev_dependencies:
very_good_analysis: ^2.4.0
flutter:
uses-material-design: true
generate: true
fonts:
- family: PixeloidSans

@ -8,6 +8,7 @@ void main() {
addBallStories(dashbook);
addLayerStories(dashbook);
addEffectsStories(dashbook);
addErrorComponentStories(dashbook);
addFlutterForestStories(dashbook);
addSparkyScorchStories(dashbook);
addAndroidAcresStories(dashbook);

@ -0,0 +1,24 @@
import 'package:flame/components.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class ErrorComponentGame extends AssetsGame {
ErrorComponentGame({required this.text});
static const description = 'Shows how ErrorComponents are rendered.';
final String text;
@override
Future<void> onLoad() async {
camera.followVector2(Vector2.zero());
await add(ErrorComponent(label: text));
await add(
ErrorComponent.bold(
label: text,
position: Vector2(0, 10),
),
);
}
}

@ -0,0 +1,16 @@
import 'package:dashbook/dashbook.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/error_component/error_component_game.dart';
void addErrorComponentStories(Dashbook dashbook) {
dashbook.storiesOf('ErrorComponent').addGame(
title: 'Basic',
description: ErrorComponentGame.description,
gameBuilder: (context) => ErrorComponentGame(
text: context.textProperty(
'label',
'Oh no, something went wrong!',
),
),
);
}

@ -4,6 +4,7 @@ export 'bottom_group/stories.dart';
export 'boundaries/stories.dart';
export 'dino_desert/stories.dart';
export 'effects/stories.dart';
export 'error_component/stories.dart';
export 'flutter_forest/stories.dart';
export 'google_word/stories.dart';
export 'launch_ramp/stories.dart';

@ -270,6 +270,13 @@ packages:
relative: true
source: path
version: "1.0.0+1"
pinball_ui:
dependency: transitive
description:
path: "../../pinball_ui"
relative: true
source: path
version: "1.0.0+1"
platform:
dependency: transitive
description:
@ -414,7 +421,7 @@ packages:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.20"
version: "6.1.0"
url_launcher_android:
dependency: transitive
description:

@ -0,0 +1,57 @@
// 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';
extension _IterableX on Iterable<Component> {
int countTexts(String value) {
return where(
(component) => component is TextComponent && component.text == value,
).length;
}
}
void main() {
group('ErrorComponent', () {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.errorBackground.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
flameTester.test('renders correctly', (game) async {
await game.ensureAdd(ErrorComponent(label: 'Error Message'));
final count = game.descendants().countTexts('Error Message');
expect(count, equals(1));
});
group('when the text is longer than one line', () {
flameTester.test('renders correctly', (game) async {
await game.ensureAdd(
ErrorComponent(
label: 'Error With A Longer Message',
),
);
final count1 = game.descendants().countTexts('Error With A');
final count2 = game.descendants().countTexts('Longer Message');
expect(count1, equals(1));
expect(count2, equals(1));
});
});
group('when using the bold font', () {
flameTester.test('renders correctly', (game) async {
await game.ensureAdd(ErrorComponent.bold(label: 'Error Message'));
final count = game.descendants().countTexts('Error Message');
expect(count, equals(1));
});
});
});
}

@ -37,6 +37,13 @@ abstract class PinballTextStyle {
fontFamily: _primaryFontFamily,
);
static const headline5 = TextStyle(
color: PinballColors.white,
fontSize: 14,
package: _fontPackage,
fontFamily: _primaryFontFamily,
);
static const subtitle2 = TextStyle(
color: PinballColors.white,
fontSize: 16,

@ -16,6 +16,7 @@ class PinballTheme {
headline2: PinballTextStyle.headline2,
headline3: PinballTextStyle.headline3,
headline4: PinballTextStyle.headline4,
headline5: PinballTextStyle.headline5,
subtitle1: PinballTextStyle.subtitle1,
subtitle2: PinballTextStyle.subtitle2,
);

@ -17,7 +17,7 @@ dependencies:
flame: ^1.1.1
flame_bloc: ^1.4.0
flame_forge2d:
git:
git:
url: https://github.com/flame-engine/flame/
path: packages/flame_forge2d/
ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f
@ -62,6 +62,7 @@ flutter:
- assets/images/components/
- assets/images/bonus_animation/
- assets/images/score/
- assets/images/link_box/
flutter_gen:
line_length: 80

@ -42,6 +42,9 @@ class _TestGame extends Forge2DGame {
AndroidAcres child, {
required GameBloc gameBloc,
}) async {
// Not needed once https://github.com/flame-engine/flame/issues/1607
// is fixed
await onLoad();
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,

@ -76,6 +76,9 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get name => '';
@override
String get rank => '';
@override
String get enterInitials => '';
@ -106,7 +109,7 @@ void main() {
bloc = _MockBackboxBloc();
whenListen(
bloc,
Stream.value(LoadingState()),
Stream<BackboxState>.empty(),
initialState: LoadingState(),
);
});
@ -121,6 +124,16 @@ void main() {
},
);
flameTester.test(
'adds LeaderboardRequested when loaded',
(game) async {
final backbox = Backbox.test(bloc: bloc);
await game.pump(backbox);
verify(() => bloc.add(LeaderboardRequested())).called(1);
},
);
flameTester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
@ -173,7 +186,7 @@ void main() {
);
whenListen(
bloc,
Stream.value(state),
Stream<BackboxState>.empty(),
initialState: state,
);
final backbox = Backbox.test(bloc: bloc);
@ -197,7 +210,7 @@ void main() {
(game) async {
whenListen(
bloc,
Stream.value(InitialsSuccessState()),
Stream<BackboxState>.empty(),
initialState: InitialsSuccessState(),
);
final backbox = Backbox.test(bloc: bloc);
@ -218,7 +231,7 @@ void main() {
(game) async {
whenListen(
bloc,
Stream.value(InitialsFailureState()),
Stream<BackboxState>.empty(),
initialState: InitialsFailureState(),
);
final backbox = Backbox.test(bloc: bloc);
@ -234,6 +247,25 @@ void main() {
},
);
flameTester.test(
'adds LeaderboardDisplay on LeaderboardSuccessState',
(game) async {
whenListen(
bloc,
Stream<BackboxState>.empty(),
initialState: LeaderboardSuccessState(entries: const []),
);
final backbox = Backbox.test(bloc: bloc);
await game.pump(backbox);
expect(
game.descendants().whereType<LeaderboardDisplay>().length,
equals(1),
);
},
);
flameTester.test(
'closes the subscription when it is removed',
(game) async {

@ -88,5 +88,41 @@ void main() {
],
);
});
group('LeaderboardRequested', () {
blocTest<BackboxBloc, BackboxState>(
'adds [LoadingState, LeaderboardSuccessState] when request succeeds',
setUp: () {
leaderboardRepository = _MockLeaderboardRepository();
when(
() => leaderboardRepository.fetchTop10Leaderboard(),
).thenAnswer(
(_) async => [LeaderboardEntryData.empty],
);
},
build: () => BackboxBloc(leaderboardRepository: leaderboardRepository),
act: (bloc) => bloc.add(LeaderboardRequested()),
expect: () => [
LoadingState(),
LeaderboardSuccessState(entries: const [LeaderboardEntryData.empty]),
],
);
blocTest<BackboxBloc, BackboxState>(
'adds [LoadingState, LeaderboardFailureState] when request fails',
setUp: () {
leaderboardRepository = _MockLeaderboardRepository();
when(
() => leaderboardRepository.fetchTop10Leaderboard(),
).thenThrow(Exception('Error'));
},
build: () => BackboxBloc(leaderboardRepository: leaderboardRepository),
act: (bloc) => bloc.add(LeaderboardRequested()),
expect: () => [
LoadingState(),
LeaderboardFailureState(),
],
);
});
});
}

@ -122,5 +122,15 @@ void main() {
);
});
});
group('LeaderboardRequested', () {
test('can be instantiated', () {
expect(LeaderboardRequested(), isNotNull);
});
test('supports value comparison', () {
expect(LeaderboardRequested(), equals(LeaderboardRequested()));
});
});
});
}

@ -1,6 +1,7 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/game/components/backbox/bloc/backbox_bloc.dart';
import 'package:pinball_theme/pinball_theme.dart';
@ -18,11 +19,30 @@ void main() {
group('LeaderboardSuccessState', () {
test('can be instantiated', () {
expect(LeaderboardSuccessState(), isNotNull);
expect(
LeaderboardSuccessState(entries: const []),
isNotNull,
);
});
test('supports value comparison', () {
expect(LeaderboardSuccessState(), equals(LeaderboardSuccessState()));
expect(
LeaderboardSuccessState(entries: const []),
equals(
LeaderboardSuccessState(entries: const []),
),
);
expect(
LeaderboardSuccessState(entries: const []),
isNot(
equals(
LeaderboardSuccessState(
entries: const [LeaderboardEntryData.empty],
),
),
),
);
});
});

@ -0,0 +1,109 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_forge2d/forge2d_game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/components/backbox/displays/leaderboard_display.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get rank => 'rank';
@override
String get score => 'score';
@override
String get name => 'name';
}
class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
await super.onLoad();
images.prefix = '';
await images.load(const AndroidTheme().leaderboardIcon.keyName);
}
Future<void> pump(LeaderboardDisplay component) {
return ensureAdd(
FlameProvider.value(
_MockAppLocalizations(),
children: [component],
),
);
}
}
void main() {
group('LeaderboardDisplay', () {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(_TestGame.new);
flameTester.test('renders the titles', (game) async {
await game.pump(LeaderboardDisplay(entries: const []));
final textComponents =
game.descendants().whereType<TextComponent>().toList();
expect(textComponents.length, equals(3));
expect(textComponents[0].text, equals('rank'));
expect(textComponents[1].text, equals('score'));
expect(textComponents[2].text, equals('name'));
});
flameTester.test('renders the entries', (game) async {
await game.pump(
LeaderboardDisplay(
entries: const [
LeaderboardEntryData(
playerInitials: 'AAA',
score: 123,
character: CharacterType.android,
),
LeaderboardEntryData(
playerInitials: 'BBB',
score: 1234,
character: CharacterType.android,
),
LeaderboardEntryData(
playerInitials: 'CCC',
score: 12345,
character: CharacterType.android,
),
LeaderboardEntryData(
playerInitials: 'DDD',
score: 12346,
character: CharacterType.android,
),
],
),
);
for (final text in [
'AAA',
'BBB',
'CCC',
'DDD',
'1st',
'2nd',
'3rd',
'4th'
]) {
expect(
game
.descendants()
.whereType<TextComponent>()
.where((textComponent) => textComponent.text == text)
.length,
equals(1),
);
}
});
});
}

@ -17,8 +17,11 @@ class _TestGame extends Forge2DGame {
]);
}
Future<void> pump(Multiballs child, {GameBloc? gameBloc}) {
return ensureAdd(
Future<void> pump(Multiballs child, {GameBloc? gameBloc}) async {
// Not needed once https://github.com/flame-engine/flame/issues/1607
// is fixed
await onLoad();
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc ?? GameBloc(),
children: [child],
@ -38,7 +41,9 @@ void main() {
setUp: (game, tester) async {
final multiballs = Multiballs();
await game.pump(multiballs);
expect(game.descendants(), contains(multiballs));
},
verify: (game, tester) async {
expect(game.descendants().whereType<Multiballs>().length, equals(1));
},
);

@ -1,5 +1,7 @@
// ignore_for_file: prefer_const_constructors
import 'dart:ui';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
@ -260,5 +262,74 @@ void main() {
findsOneWidget,
);
});
testWidgets('hide a hud on game over', (tester) async {
final startGameState = StartGameState.initial().copyWith(
status: StartGameStatus.play,
);
final gameState = GameState.initial().copyWith(
status: GameStatus.gameOver,
);
whenListen(
startGameBloc,
Stream.value(startGameState),
initialState: startGameState,
);
whenListen(
gameBloc,
Stream.value(gameState),
initialState: gameState,
);
await tester.pumpApp(
PinballGameView(game: game),
gameBloc: gameBloc,
startGameBloc: startGameBloc,
);
expect(
find.byType(GameHud),
findsNothing,
);
});
testWidgets('keep focus on game when mouse hovers over it', (tester) async {
final startGameState = StartGameState.initial().copyWith(
status: StartGameStatus.play,
);
final gameState = GameState.initial().copyWith(
status: GameStatus.gameOver,
);
whenListen(
startGameBloc,
Stream.value(startGameState),
initialState: startGameState,
);
whenListen(
gameBloc,
Stream.value(gameState),
initialState: gameState,
);
await tester.pumpApp(
PinballGameView(game: game),
gameBloc: gameBloc,
startGameBloc: startGameBloc,
);
game.focusNode.unfocus();
await tester.pump();
expect(game.focusNode.hasFocus, isFalse);
final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(location: Offset.zero);
addTearDown(gesture.removePointer);
await gesture.moveTo((game.size / 2).toOffset());
await tester.pump();
expect(game.focusNode.hasFocus, isTrue);
});
});
}

Loading…
Cancel
Save