diff --git a/assets/images/bonus_animation/android_spaceship.png b/assets/images/bonus_animation/android_spaceship.png index 200e1c6c..66cc8b6a 100644 Binary files a/assets/images/bonus_animation/android_spaceship.png and b/assets/images/bonus_animation/android_spaceship.png differ diff --git a/assets/images/bonus_animation/dash_nest.png b/assets/images/bonus_animation/dash_nest.png index 210f17cb..42aa8b9e 100644 Binary files a/assets/images/bonus_animation/dash_nest.png and b/assets/images/bonus_animation/dash_nest.png differ diff --git a/assets/images/bonus_animation/dino_chomp.png b/assets/images/bonus_animation/dino_chomp.png index a8a9cfe3..c6825c0b 100644 Binary files a/assets/images/bonus_animation/dino_chomp.png and b/assets/images/bonus_animation/dino_chomp.png differ diff --git a/assets/images/bonus_animation/google_word.png b/assets/images/bonus_animation/google_word.png index c4ab2948..fa07da65 100644 Binary files a/assets/images/bonus_animation/google_word.png and b/assets/images/bonus_animation/google_word.png differ diff --git a/assets/images/bonus_animation/sparky_turbo_charge.png b/assets/images/bonus_animation/sparky_turbo_charge.png index 8b3491e8..c5d2d9d3 100644 Binary files a/assets/images/bonus_animation/sparky_turbo_charge.png and b/assets/images/bonus_animation/sparky_turbo_charge.png differ diff --git a/assets/images/link_box/info_icon.png b/assets/images/link_box/info_icon.png deleted file mode 100644 index 78556be0..00000000 Binary files a/assets/images/link_box/info_icon.png and /dev/null differ diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 89f03dfa..97ef8d63 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -9,21 +9,29 @@ import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/start_game.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_ui/pinball_ui.dart'; +import 'package:platform_helper/platform_helper.dart'; +import 'package:share_repository/share_repository.dart'; class App extends StatelessWidget { const App({ Key? key, required AuthenticationRepository authenticationRepository, required LeaderboardRepository leaderboardRepository, + required ShareRepository shareRepository, required PinballAudioPlayer pinballAudioPlayer, + required PlatformHelper platformHelper, }) : _authenticationRepository = authenticationRepository, _leaderboardRepository = leaderboardRepository, + _shareRepository = shareRepository, _pinballAudioPlayer = pinballAudioPlayer, + _platformHelper = platformHelper, super(key: key); final AuthenticationRepository _authenticationRepository; final LeaderboardRepository _leaderboardRepository; + final ShareRepository _shareRepository; final PinballAudioPlayer _pinballAudioPlayer; + final PlatformHelper _platformHelper; @override Widget build(BuildContext context) { @@ -31,7 +39,9 @@ class App extends StatelessWidget { providers: [ RepositoryProvider.value(value: _authenticationRepository), RepositoryProvider.value(value: _leaderboardRepository), + RepositoryProvider.value(value: _shareRepository), RepositoryProvider.value(value: _pinballAudioPlayer), + RepositoryProvider.value(value: _platformHelper), ], child: MultiBlocProvider( providers: [ diff --git a/lib/assets_manager/cubit/assets_manager_cubit.dart b/lib/assets_manager/cubit/assets_manager_cubit.dart index 65b780c2..eb0f7e31 100644 --- a/lib/assets_manager/cubit/assets_manager_cubit.dart +++ b/lib/assets_manager/cubit/assets_manager_cubit.dart @@ -18,7 +18,7 @@ class AssetsManagerCubit extends Cubit { /// delay here, which is a bit random in duration but enough to let the UI /// do its job without adding too much delay for the user, we are letting /// the UI paint first, and then we start loading the assets. - await Future.delayed(const Duration(milliseconds: 300)); + await Future.delayed(const Duration(seconds: 1)); emit( state.copyWith( loadables: [ diff --git a/lib/assets_manager/views/assets_loading_page.dart b/lib/assets_manager/views/assets_loading_page.dart index 4e75a3a5..72476064 100644 --- a/lib/assets_manager/views/assets_loading_page.dart +++ b/lib/assets_manager/views/assets_loading_page.dart @@ -17,29 +17,32 @@ class AssetsLoadingPage extends StatelessWidget { Widget build(BuildContext context) { final l10n = context.l10n; final headline1 = Theme.of(context).textTheme.headline1; - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Assets.images.loadingGame.ioPinball.image(), - ), - const SizedBox(height: 40), - AnimatedEllipsisText( - l10n.loading, - style: headline1, - ), - const SizedBox(height: 40), - FractionallySizedBox( - widthFactor: 0.8, - child: BlocBuilder( - builder: (context, state) { - return PinballLoadingIndicator(value: state.progress); - }, + return Container( + decoration: const CrtBackground(), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Assets.images.loadingGame.ioPinball.image(), ), - ), - ], + const SizedBox(height: 40), + AnimatedEllipsisText( + l10n.loading, + style: headline1, + ), + const SizedBox(height: 40), + FractionallySizedBox( + widthFactor: 0.8, + child: BlocBuilder( + builder: (context, state) { + return PinballLoadingIndicator(value: state.progress); + }, + ), + ), + ], + ), ), ); } diff --git a/lib/game/behaviors/ball_theming_behavior.dart b/lib/game/behaviors/ball_theming_behavior.dart deleted file mode 100644 index 5e12a720..00000000 --- a/lib/game/behaviors/ball_theming_behavior.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flame_bloc/flame_bloc.dart'; -import 'package:pinball/select_character/select_character.dart'; -import 'package:pinball_components/pinball_components.dart'; - -/// Updates the launch [Ball] to reflect character selections. -class BallThemingBehavior extends Component - with - FlameBlocListenable, - HasGameRef { - @override - void onNewState(CharacterThemeState state) { - gameRef - .descendants() - .whereType() - .single - .bloc - .onThemeChanged(state.characterTheme); - } -} diff --git a/lib/game/behaviors/behaviors.dart b/lib/game/behaviors/behaviors.dart index 5900f2b3..bb196cec 100644 --- a/lib/game/behaviors/behaviors.dart +++ b/lib/game/behaviors/behaviors.dart @@ -1,7 +1,9 @@ export 'ball_spawning_behavior.dart'; -export 'ball_theming_behavior.dart'; export 'bonus_ball_spawning_behavior.dart'; export 'bonus_noise_behavior.dart'; export 'bumper_noise_behavior.dart'; export 'camera_focusing_behavior.dart'; +export 'character_selection_behavior.dart'; +export 'cow_bumper_noise_behavior.dart'; +export 'kicker_noise_behavior.dart'; export 'scoring_behavior.dart'; diff --git a/lib/game/behaviors/character_selection_behavior.dart b/lib/game/behaviors/character_selection_behavior.dart new file mode 100644 index 00000000..e62438f6 --- /dev/null +++ b/lib/game/behaviors/character_selection_behavior.dart @@ -0,0 +1,31 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball/select_character/select_character.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:platform_helper/platform_helper.dart'; + +/// Updates the [ArcadeBackground] and launch [Ball] to reflect character +/// selections. +class CharacterSelectionBehavior extends Component + with + FlameBlocListenable, + HasGameRef { + @override + void onNewState(CharacterThemeState state) { + if (!readProvider().isMobile) { + gameRef + .descendants() + .whereType() + .single + .bloc + .onCharacterSelected(state.characterTheme); + } + gameRef + .descendants() + .whereType() + .single + .bloc + .onCharacterSelected(state.characterTheme); + } +} diff --git a/lib/game/behaviors/cow_bumper_noise_behavior.dart b/lib/game/behaviors/cow_bumper_noise_behavior.dart new file mode 100644 index 00000000..14ad1307 --- /dev/null +++ b/lib/game/behaviors/cow_bumper_noise_behavior.dart @@ -0,0 +1,13 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class CowBumperNoiseBehavior extends ContactBehavior { + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + readProvider().play(PinballAudio.cowMoo); + } +} diff --git a/lib/game/behaviors/kicker_noise_behavior.dart b/lib/game/behaviors/kicker_noise_behavior.dart new file mode 100644 index 00000000..a04ffeff --- /dev/null +++ b/lib/game/behaviors/kicker_noise_behavior.dart @@ -0,0 +1,11 @@ +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class KickerNoiseBehavior extends ContactBehavior { + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + readProvider().play(PinballAudio.kicker); + } +} diff --git a/lib/game/bloc/game_bloc.dart b/lib/game/bloc/game_bloc.dart index 795d04e2..c63bf514 100644 --- a/lib/game/bloc/game_bloc.dart +++ b/lib/game/bloc/game_bloc.dart @@ -16,6 +16,8 @@ class GameBloc extends Bloc { on(_onGameStarted); } + static const _maxScore = 9999999999; + void _onGameStarted(GameStarted _, Emitter emit) { emit(state.copyWith(status: GameStatus.playing)); } @@ -25,7 +27,10 @@ class GameBloc extends Bloc { } void _onRoundLost(RoundLost event, Emitter emit) { - final score = state.totalScore + state.roundScore * state.multiplier; + final score = math.min( + state.totalScore + state.roundScore * state.multiplier, + _maxScore, + ); final roundsLeft = math.max(state.rounds - 1, 0); emit( @@ -41,9 +46,11 @@ class GameBloc extends Bloc { void _onScored(Scored event, Emitter emit) { if (state.status.isPlaying) { - emit( - state.copyWith(roundScore: state.roundScore + event.points), + final combinedScore = math.min( + state.totalScore + state.roundScore + event.points, + _maxScore, ); + emit(state.copyWith(roundScore: combinedScore - state.totalScore)); } } diff --git a/lib/game/components/android_acres/android_acres.dart b/lib/game/components/android_acres/android_acres.dart index 902eb11c..fd59ace3 100644 --- a/lib/game/components/android_acres/android_acres.dart +++ b/lib/game/components/android_acres/android_acres.dart @@ -48,6 +48,7 @@ class AndroidAcres extends Component { children: [ ScoringContactBehavior(points: Points.twentyThousand), BumperNoiseBehavior(), + CowBumperNoiseBehavior(), ], )..initialPosition = Vector2(-20.7, -13), AndroidSpaceshipBonusBehavior(), diff --git a/lib/game/components/backbox/backbox.dart b/lib/game/components/backbox/backbox.dart index e455e89f..4ce44348 100644 --- a/lib/game/components/backbox/backbox.dart +++ b/lib/game/components/backbox/backbox.dart @@ -6,10 +6,13 @@ import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/game/components/backbox/bloc/backbox_bloc.dart'; import 'package:pinball/game/components/backbox/displays/displays.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/l10n/l10n.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' hide Assets; +import 'package:pinball_ui/pinball_ui.dart'; import 'package:platform_helper/platform_helper.dart'; +import 'package:share_repository/share_repository.dart'; /// {@template backbox} /// The [Backbox] of the pinball machine. @@ -18,24 +21,25 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef { /// {@macro backbox} Backbox({ required LeaderboardRepository leaderboardRepository, + required ShareRepository shareRepository, required List? entries, }) : _bloc = BackboxBloc( leaderboardRepository: leaderboardRepository, initialEntries: entries, ), - _platformHelper = PlatformHelper(); + _shareRepository = shareRepository; /// {@macro backbox} @visibleForTesting Backbox.test({ required BackboxBloc bloc, - required PlatformHelper platformHelper, + required ShareRepository shareRepository, }) : _bloc = bloc, - _platformHelper = platformHelper; + _shareRepository = shareRepository; + final ShareRepository _shareRepository; late final Component _display; final BackboxBloc _bloc; - final PlatformHelper _platformHelper; late StreamSubscription _subscription; @override @@ -68,7 +72,7 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef { } else if (state is LeaderboardFailureState) { _display.add(LeaderboardFailureDisplay()); } else if (state is InitialsFormState) { - if (_platformHelper.isMobile) { + if (readProvider().isMobile) { gameRef.overlays.add(PinballGame.mobileControlsOverlay); } _display.add( @@ -87,6 +91,8 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef { ), ); } else if (state is InitialsSuccessState) { + gameRef.overlays.remove(PinballGame.mobileControlsOverlay); + _display.add( GameOverInfoDisplay( onShare: () { @@ -94,6 +100,20 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef { }, ), ); + } else if (state is ShareState) { + _display.add( + ShareDisplay( + onShare: (platform) { + final message = readProvider() + .iGotScoreAtPinball(state.score.formatScore()); + final url = _shareRepository.shareText( + value: message, + platform: platform, + ); + openLink(url); + }, + ), + ); } else if (state is InitialsFailureState) { _display.add( InitialsSubmissionFailureDisplay( diff --git a/lib/game/components/backbox/bloc/backbox_bloc.dart b/lib/game/components/backbox/bloc/backbox_bloc.dart index 8a89b1bd..cfac4c6f 100644 --- a/lib/game/components/backbox/bloc/backbox_bloc.dart +++ b/lib/game/components/backbox/bloc/backbox_bloc.dart @@ -75,9 +75,7 @@ class BackboxBloc extends Bloc { Emitter emit, ) async { emit( - ShareState( - score: event.score, - ), + ShareState(score: event.score), ); } diff --git a/lib/game/components/backbox/displays/displays.dart b/lib/game/components/backbox/displays/displays.dart index 2b8a38ae..f80c0b54 100644 --- a/lib/game/components/backbox/displays/displays.dart +++ b/lib/game/components/backbox/displays/displays.dart @@ -5,3 +5,4 @@ export 'initials_submission_success_display.dart'; export 'leaderboard_display.dart'; export 'leaderboard_failure_display.dart'; export 'loading_display.dart'; +export 'share_display.dart'; diff --git a/lib/game/components/backbox/displays/game_over_info_display.dart b/lib/game/components/backbox/displays/game_over_info_display.dart index c7708bde..52939345 100644 --- a/lib/game/components/backbox/displays/game_over_info_display.dart +++ b/lib/game/components/backbox/displays/game_over_info_display.dart @@ -212,7 +212,7 @@ class GoogleIOLinkComponent extends TextComponent with HasGameRef, Tappable { ); @override - bool onTapDown(TapDownInfo info) { + bool onTapUp(TapUpInfo info) { openLink(ShareRepository.googleIOEvent); return true; } diff --git a/lib/game/components/backbox/displays/leaderboard_display.dart b/lib/game/components/backbox/displays/leaderboard_display.dart index ac7a798d..ab418ccc 100644 --- a/lib/game/components/backbox/displays/leaderboard_display.dart +++ b/lib/game/components/backbox/displays/leaderboard_display.dart @@ -1,4 +1,6 @@ +// cSpell:ignore sublist import 'package:flame/components.dart'; +import 'package:flame/effects.dart'; import 'package:flutter/material.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/l10n/l10n.dart'; @@ -23,6 +25,23 @@ final _bodyTextPaint = TextPaint( ), ); +double _calcY(int i) => (i * 3.2) + 3.2; + +const _columns = [-14.0, 0.0, 14.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'; + } +} + /// {@template leaderboard_display} /// Component that builds the leaderboard list of the Backbox. /// {@endtemplate} @@ -33,21 +52,47 @@ class LeaderboardDisplay extends PositionComponent with HasGameRef { final List _entries; - double _calcY(int i) => (i * 3.2) + 3.2; + _MovePageArrow _findArrow({required bool active}) { + return descendants() + .whereType<_MovePageArrow>() + .firstWhere((arrow) => arrow.active == active); + } - static const _columns = [-15.0, 0.0, 15.0]; + void _changePage(List ranking, int offset) { + final current = descendants().whereType<_RankingPage>().single; + final activeArrow = _findArrow(active: true); + final inactiveArrow = _findArrow(active: false); - 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'; - } + activeArrow.active = false; + + current.add( + ScaleEffect.to( + Vector2(0, 1), + EffectController( + duration: 0.5, + curve: Curves.easeIn, + ), + )..onFinishCallback = () { + current.removeFromParent(); + inactiveArrow.active = true; + firstChild()?.add( + _RankingPage( + ranking: ranking, + offset: offset, + ) + ..scale = Vector2(0, 1) + ..add( + ScaleEffect.to( + Vector2(1, 1), + EffectController( + duration: 0.5, + curve: Curves.easeIn, + ), + ), + ), + ); + }, + ); } @override @@ -60,6 +105,20 @@ class LeaderboardDisplay extends PositionComponent with HasGameRef { PositionComponent( position: Vector2(0, 4), children: [ + _MovePageArrow( + position: Vector2(20, 9), + onTap: () { + _changePage(_entries.sublist(5), 5); + }, + ), + _MovePageArrow( + position: Vector2(-20, 9), + direction: ArrowIconDirection.left, + active: false, + onTap: () { + _changePage(_entries.take(5).toList(), 0); + }, + ), PositionComponent( children: [ TextComponent( @@ -82,39 +141,106 @@ class LeaderboardDisplay extends PositionComponent with HasGameRef { ), ], ), - 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, - ), - ], - ), + _RankingPage( + ranking: ranking, + offset: 0, + ), ], ), ); } } + +class _RankingPage extends PositionComponent with HasGameRef { + _RankingPage({ + required this.ranking, + required this.offset, + }) : super(children: []); + + final List ranking; + final int offset; + + @override + Future onLoad() async { + await addAll([ + for (var i = 0; i < ranking.length; i++) + PositionComponent( + children: [ + TextComponent( + text: _rank(i + 1 + offset), + 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] - 3, _calcY(i) + .25), + ), + TextComponent( + text: ranking[i].playerInitials, + textRenderer: _bodyTextPaint, + position: Vector2(_columns[2] + 1, _calcY(i)), + anchor: Anchor.center, + ), + ], + ), + ]); + } +} + +class _MovePageArrow extends PositionComponent { + _MovePageArrow({ + required Vector2 position, + required this.onTap, + this.direction = ArrowIconDirection.right, + bool active = true, + }) : super( + position: position, + children: [ + if (active) + ArrowIcon( + position: Vector2.zero(), + direction: direction, + onTap: onTap, + ), + SequenceEffect( + [ + ScaleEffect.to( + Vector2.all(1.2), + EffectController(duration: 1), + ), + ScaleEffect.to(Vector2.all(1), EffectController(duration: 1)), + ], + infinite: true, + ), + ], + ); + + final ArrowIconDirection direction; + final VoidCallback onTap; + + bool get active => children.whereType().isNotEmpty; + set active(bool value) { + if (value) { + add( + ArrowIcon( + position: Vector2.zero(), + direction: direction, + onTap: onTap, + ), + ); + } else { + firstChild()?.removeFromParent(); + } + } +} diff --git a/lib/game/components/backbox/displays/share_display.dart b/lib/game/components/backbox/displays/share_display.dart new file mode 100644 index 00000000..0bf61e7d --- /dev/null +++ b/lib/game/components/backbox/displays/share_display.dart @@ -0,0 +1,189 @@ +import 'dart:async'; + +import 'package:flame/components.dart'; +import 'package:flame/input.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:pinball_ui/pinball_ui.dart'; +import 'package:share_repository/share_repository.dart'; + +/// Signature for the callback called when the user tries to share their score +/// on the [ShareDisplay]. +typedef OnSocialShareTap = void Function(SharePlatform); + +final _descriptionTextPaint = TextPaint( + style: const TextStyle( + fontSize: 1.6, + color: PinballColors.white, + fontFamily: PinballFonts.pixeloidSans, + ), +); + +/// {@template share_display} +/// Display that allows users to share their score to social networks. +/// {@endtemplate} +class ShareDisplay extends Component with HasGameRef { + /// {@macro share_display} + ShareDisplay({ + OnSocialShareTap? onShare, + }) : super( + children: [ + _ShareInstructionsComponent( + onShare: onShare, + ), + ], + ); +} + +class _ShareInstructionsComponent extends PositionComponent with HasGameRef { + _ShareInstructionsComponent({ + OnSocialShareTap? onShare, + }) : super( + anchor: Anchor.center, + position: Vector2(0, -25), + children: [ + _DescriptionComponent(), + _SocialNetworksComponent( + onShare: onShare, + ), + ], + ); +} + +class _DescriptionComponent extends PositionComponent with HasGameRef { + _DescriptionComponent() + : super( + anchor: Anchor.center, + position: Vector2.zero(), + children: [ + _LetEveryoneTextComponent(), + _SharingYourScoreTextComponent(), + _SocialMediaTextComponent(), + ], + ); +} + +class _LetEveryoneTextComponent extends TextComponent with HasGameRef { + _LetEveryoneTextComponent() + : super( + anchor: Anchor.center, + position: Vector2.zero(), + textRenderer: _descriptionTextPaint, + ); + + @override + Future onLoad() async { + await super.onLoad(); + text = readProvider().letEveryone; + } +} + +class _SharingYourScoreTextComponent extends TextComponent with HasGameRef { + _SharingYourScoreTextComponent() + : super( + anchor: Anchor.center, + position: Vector2(0, 2.5), + textRenderer: _descriptionTextPaint, + ); + + @override + Future onLoad() async { + await super.onLoad(); + text = readProvider().bySharingYourScore; + } +} + +class _SocialMediaTextComponent extends TextComponent with HasGameRef { + _SocialMediaTextComponent() + : super( + anchor: Anchor.center, + position: Vector2(0, 5), + textRenderer: _descriptionTextPaint, + ); + + @override + Future onLoad() async { + await super.onLoad(); + text = readProvider().socialMediaAccount; + } +} + +class _SocialNetworksComponent extends PositionComponent with HasGameRef { + _SocialNetworksComponent({ + OnSocialShareTap? onShare, + }) : super( + anchor: Anchor.center, + position: Vector2(0, 12), + children: [ + FacebookButtonComponent(onTap: onShare), + TwitterButtonComponent(onTap: onShare), + ], + ); +} + +/// {@template facebook_button_component} +/// Button for sharing on Facebook. +/// {@endtemplate} +class FacebookButtonComponent extends SpriteComponent + with HasGameRef, Tappable { + /// {@macro facebook_button_component} + FacebookButtonComponent({ + OnSocialShareTap? onTap, + }) : _onTap = onTap, + super( + anchor: Anchor.center, + position: Vector2(-5, 0), + ); + + final OnSocialShareTap? _onTap; + + @override + bool onTapUp(TapUpInfo info) { + _onTap?.call(SharePlatform.facebook); + return true; + } + + @override + Future onLoad() async { + await super.onLoad(); + final sprite = Sprite( + gameRef.images.fromCache(Assets.images.backbox.button.facebook.keyName), + ); + this.sprite = sprite; + size = sprite.originalSize / 25; + } +} + +/// {@template twitter_button_component} +/// Button for sharing on Twitter. +/// {@endtemplate} +class TwitterButtonComponent extends SpriteComponent with HasGameRef, Tappable { + /// {@macro twitter_button_component} + TwitterButtonComponent({ + OnSocialShareTap? onTap, + }) : _onTap = onTap, + super( + anchor: Anchor.center, + position: Vector2(5, 0), + ); + + final OnSocialShareTap? _onTap; + + @override + bool onTapUp(TapUpInfo info) { + _onTap?.call(SharePlatform.twitter); + return true; + } + + @override + Future onLoad() async { + await super.onLoad(); + final sprite = Sprite( + gameRef.images.fromCache(Assets.images.backbox.button.twitter.keyName), + ); + this.sprite = sprite; + size = sprite.originalSize / 25; + } +} diff --git a/lib/game/components/bottom_group.dart b/lib/game/components/bottom_group.dart index bc644f96..cfa7e434 100644 --- a/lib/game/components/bottom_group.dart +++ b/lib/game/components/bottom_group.dart @@ -52,6 +52,7 @@ class _BottomGroupSide extends Component { children: [ ScoringContactBehavior(points: Points.fiveThousand) ..applyTo(['bouncy_edge']), + KickerNoiseBehavior()..applyTo(['bouncy_edge']), ], )..initialPosition = Vector2( (22.44 * direction) + centerXAdjustment, diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 45376f87..103f029c 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -5,7 +5,7 @@ export 'dino_desert/dino_desert.dart'; export 'drain/drain.dart'; export 'flutter_forest/flutter_forest.dart'; export 'game_bloc_status_listener.dart'; -export 'google_word/google_word.dart'; +export 'google_gallery/google_gallery.dart'; export 'launcher.dart'; export 'multiballs/multiballs.dart'; export 'multipliers/multipliers.dart'; diff --git a/lib/game/components/google_word/behaviors/behaviors.dart b/lib/game/components/google_gallery/behaviors/behaviors.dart similarity index 100% rename from lib/game/components/google_word/behaviors/behaviors.dart rename to lib/game/components/google_gallery/behaviors/behaviors.dart diff --git a/lib/game/components/google_gallery/behaviors/google_word_bonus_behavior.dart b/lib/game/components/google_gallery/behaviors/google_word_bonus_behavior.dart new file mode 100644 index 00000000..787fcefc --- /dev/null +++ b/lib/game/components/google_gallery/behaviors/google_word_bonus_behavior.dart @@ -0,0 +1,26 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// Adds a [GameBonus.googleWord] when all [GoogleLetter]s are activated. +class GoogleWordBonusBehavior extends Component { + @override + Future onLoad() async { + await super.onLoad(); + await add( + FlameBlocListener( + listenWhen: (_, state) => state.letterSpriteStates.values + .every((element) => element == GoogleLetterSpriteState.lit), + onNewState: (state) { + readBloc() + .add(const BonusActivated(GameBonus.googleWord)); + readBloc().onBonusAwarded(); + add(BonusBallSpawningBehavior()); + }, + ), + ); + } +} diff --git a/lib/game/components/google_gallery/google_gallery.dart b/lib/game/components/google_gallery/google_gallery.dart new file mode 100644 index 00000000..0b3d4b10 --- /dev/null +++ b/lib/game/components/google_gallery/google_gallery.dart @@ -0,0 +1,47 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/game/components/google_gallery/behaviors/behaviors.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// {@template google_gallery} +/// Middle section of the board containing the [GoogleWord] and the +/// [GoogleRollover]s. +/// {@endtemplate} +class GoogleGallery extends Component with ZIndex { + /// {@macro google_gallery} + GoogleGallery() + : super( + children: [ + FlameBlocProvider( + create: GoogleWordCubit.new, + children: [ + GoogleRollover( + side: BoardSide.right, + children: [ + ScoringContactBehavior(points: Points.fiveThousand), + ], + ), + GoogleRollover( + side: BoardSide.left, + children: [ + ScoringContactBehavior(points: Points.fiveThousand), + ], + ), + GoogleWord(position: Vector2(-4.45, 1.8)), + GoogleWordBonusBehavior(), + ], + ), + ], + ) { + zIndex = ZIndexes.decal; + } + + /// Creates a [GoogleGallery] without any children. + /// + /// This can be used for testing [GoogleGallery]'s behaviors in isolation. + @visibleForTesting + GoogleGallery.test(); +} diff --git a/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart b/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart deleted file mode 100644 index c1c14ed5..00000000 --- a/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flame_bloc/flame_bloc.dart'; -import 'package:pinball/game/game.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -/// Adds a [GameBonus.googleWord] when all [GoogleLetter]s are activated. -class GoogleWordBonusBehavior extends Component - with ParentIsA, FlameBlocReader { - @override - void onMount() { - super.onMount(); - - final googleLetters = parent.children.whereType(); - for (final letter in googleLetters) { - letter.bloc.stream.listen((_) { - final achievedBonus = googleLetters - .every((letter) => letter.bloc.state == GoogleLetterState.lit); - - if (achievedBonus) { - bloc.add(const BonusActivated(GameBonus.googleWord)); - for (final letter in googleLetters) { - letter.bloc.onReset(); - } - } - }); - } - } -} diff --git a/lib/game/components/google_word/google_word.dart b/lib/game/components/google_word/google_word.dart deleted file mode 100644 index 76bac244..00000000 --- a/lib/game/components/google_word/google_word.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flutter/material.dart'; -import 'package:pinball/game/behaviors/scoring_behavior.dart'; -import 'package:pinball/game/components/google_word/behaviors/behaviors.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -/// {@template google_word} -/// Loads all [GoogleLetter]s to compose a [GoogleWord]. -/// {@endtemplate} -class GoogleWord extends Component with ZIndex { - /// {@macro google_word} - GoogleWord({ - required Vector2 position, - }) : super( - children: [ - GoogleLetter( - 0, - children: [ScoringContactBehavior(points: Points.fiveThousand)], - )..initialPosition = position + Vector2(-13.1, 1.72), - GoogleLetter( - 1, - children: [ScoringContactBehavior(points: Points.fiveThousand)], - )..initialPosition = position + Vector2(-8.33, -0.75), - GoogleLetter( - 2, - children: [ScoringContactBehavior(points: Points.fiveThousand)], - )..initialPosition = position + Vector2(-2.88, -1.85), - GoogleLetter( - 3, - children: [ScoringContactBehavior(points: Points.fiveThousand)], - )..initialPosition = position + Vector2(2.88, -1.85), - GoogleLetter( - 4, - children: [ScoringContactBehavior(points: Points.fiveThousand)], - )..initialPosition = position + Vector2(8.33, -0.75), - GoogleLetter( - 5, - children: [ScoringContactBehavior(points: Points.fiveThousand)], - )..initialPosition = position + Vector2(13.1, 1.72), - GoogleWordBonusBehavior(), - ], - ) { - zIndex = ZIndexes.decal; - } - - /// Creates a [GoogleWord] without any children. - /// - /// This can be used for testing [GoogleWord]'s behaviors in isolation. - @visibleForTesting - GoogleWord.test(); -} diff --git a/lib/game/components/multiballs/behaviors/multiballs_behavior.dart b/lib/game/components/multiballs/behaviors/multiballs_behavior.dart index b01c32e1..1d3aa16c 100644 --- a/lib/game/components/multiballs/behaviors/multiballs_behavior.dart +++ b/lib/game/components/multiballs/behaviors/multiballs_behavior.dart @@ -11,7 +11,8 @@ class MultiballsBehavior extends Component bool listenWhen(GameState? previousState, GameState newState) { final hasChanged = previousState?.bonusHistory != newState.bonusHistory; final lastBonusIsMultiball = newState.bonusHistory.isNotEmpty && - newState.bonusHistory.last == GameBonus.dashNest; + (newState.bonusHistory.last == GameBonus.dashNest || + newState.bonusHistory.last == GameBonus.googleWord); return hasChanged && lastBonusIsMultiball; } diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index e0b66b15..fccd494e 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -12,7 +12,7 @@ extension PinballGameAssetsX on PinballGame { const androidTheme = AndroidTheme(); const dinoTheme = DinoTheme(); - return [ + final gameAssets = [ images.load(components.Assets.images.boardBackground.keyName), images.load(components.Assets.images.ball.flameEffect.keyName), images.load(components.Assets.images.signpost.inactive.keyName), @@ -101,6 +101,8 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.sparky.bumper.c.dimmed.keyName), images.load(components.Assets.images.backbox.marquee.keyName), images.load(components.Assets.images.backbox.displayDivider.keyName), + images.load(components.Assets.images.backbox.button.facebook.keyName), + images.load(components.Assets.images.backbox.button.twitter.keyName), images.load( components.Assets.images.backbox.displayTitleDecoration.keyName, ), @@ -116,6 +118,10 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.googleWord.letter5.dimmed.keyName), images.load(components.Assets.images.googleWord.letter6.lit.keyName), images.load(components.Assets.images.googleWord.letter6.dimmed.keyName), + images.load(components.Assets.images.googleRollover.left.decal.keyName), + images.load(components.Assets.images.googleRollover.left.pin.keyName), + images.load(components.Assets.images.googleRollover.right.decal.keyName), + images.load(components.Assets.images.googleRollover.right.pin.keyName), images.load(components.Assets.images.multiball.lit.keyName), images.load(components.Assets.images.multiball.dimmed.keyName), images.load(components.Assets.images.multiplier.x2.lit.keyName), @@ -139,14 +145,24 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.skillShot.pin.keyName), images.load(components.Assets.images.skillShot.lit.keyName), images.load(components.Assets.images.skillShot.dimmed.keyName), - images.load(dashTheme.leaderboardIcon.keyName), - images.load(sparkyTheme.leaderboardIcon.keyName), + images.load(components.Assets.images.displayArrows.arrowLeft.keyName), + images.load(components.Assets.images.displayArrows.arrowRight.keyName), images.load(androidTheme.leaderboardIcon.keyName), - images.load(dinoTheme.leaderboardIcon.keyName), images.load(androidTheme.ball.keyName), + images.load(dashTheme.leaderboardIcon.keyName), images.load(dashTheme.ball.keyName), + images.load(dinoTheme.leaderboardIcon.keyName), images.load(dinoTheme.ball.keyName), + images.load(sparkyTheme.leaderboardIcon.keyName), images.load(sparkyTheme.ball.keyName), ]; + + return (platformHelper.isMobile) ? gameAssets : gameAssets + ..addAll([ + images.load(androidTheme.background.keyName), + images.load(dashTheme.background.keyName), + images.load(dinoTheme.background.keyName), + images.load(sparkyTheme.background.keyName), + ]); } } diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 9ff48987..ad17b856 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -14,15 +14,19 @@ import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; +import 'package:platform_helper/platform_helper.dart'; +import 'package:share_repository/share_repository.dart'; class PinballGame extends PinballForge2DGame with HasKeyboardHandlerComponents, MultiTouchTapDetector, HasTappables { PinballGame({ required CharacterThemeCubit characterThemeBloc, required this.leaderboardRepository, + required this.shareRepository, required GameBloc gameBloc, required AppLocalizations l10n, required PinballAudioPlayer audioPlayer, + required this.platformHelper, }) : focusNode = FocusNode(), _gameBloc = gameBloc, _audioPlayer = audioPlayer, @@ -51,8 +55,12 @@ class PinballGame extends PinballForge2DGame final LeaderboardRepository leaderboardRepository; + final ShareRepository shareRepository; + final AppLocalizations _l10n; + final PlatformHelper platformHelper; + final GameBloc _gameBloc; List? _entries; @@ -84,13 +92,15 @@ class PinballGame extends PinballForge2DGame providers: [ FlameProvider.value(_audioPlayer), FlameProvider.value(leaderboardRepository), + FlameProvider.value(shareRepository), FlameProvider.value(_l10n), + FlameProvider.value(platformHelper), ], children: [ BonusNoiseBehavior(), GameBlocStatusListener(), BallSpawningBehavior(), - BallThemingBehavior(), + CharacterSelectionBehavior(), CameraFocusingBehavior(), CanvasComponent( onSpritePainted: (paint) { @@ -101,13 +111,15 @@ class PinballGame extends PinballForge2DGame children: [ ZCanvasComponent( children: [ + if (!platformHelper.isMobile) ArcadeBackground(), BoardBackgroundSpriteComponent(), Boundaries(), Backbox( leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, entries: _entries, ), - GoogleWord(position: Vector2(-4.45, 1.8)), + GoogleGallery(), Multipliers(), Multiballs(), SkillShot( @@ -193,14 +205,18 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector { DebugPinballGame({ required CharacterThemeCubit characterThemeBloc, required LeaderboardRepository leaderboardRepository, + required ShareRepository shareRepository, required AppLocalizations l10n, required PinballAudioPlayer audioPlayer, + required PlatformHelper platformHelper, required GameBloc gameBloc, }) : super( characterThemeBloc: characterThemeBloc, audioPlayer: audioPlayer, leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, l10n: l10n, + platformHelper: platformHelper, gameBloc: gameBloc, ); diff --git a/lib/game/view/pinball_game_page.dart b/lib/game/view/pinball_game_page.dart index 5c21f257..efc11996 100644 --- a/lib/game/view/pinball_game_page.dart +++ b/lib/game/view/pinball_game_page.dart @@ -5,13 +5,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/assets_manager/assets_manager.dart'; import 'package:pinball/game/game.dart'; -import 'package:pinball/gen/gen.dart'; import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/more_information/more_information.dart'; import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/start_game.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_ui/pinball_ui.dart'; +import 'package:platform_helper/platform_helper.dart'; +import 'package:share_repository/share_repository.dart'; class PinballGamePage extends StatelessWidget { const PinballGamePage({ @@ -26,31 +27,34 @@ class PinballGamePage extends StatelessWidget { final characterThemeBloc = context.read(); final audioPlayer = context.read(); final leaderboardRepository = context.read(); + final shareRepository = context.read(); + final platformHelper = context.read(); final gameBloc = context.read(); final game = isDebugMode ? DebugPinballGame( characterThemeBloc: characterThemeBloc, audioPlayer: audioPlayer, leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, l10n: context.l10n, + platformHelper: platformHelper, gameBloc: gameBloc, ) : PinballGame( characterThemeBloc: characterThemeBloc, audioPlayer: audioPlayer, leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, l10n: context.l10n, + platformHelper: platformHelper, gameBloc: gameBloc, ); - return Container( - decoration: const CrtBackground(), - child: Scaffold( - backgroundColor: PinballColors.transparent, - body: BlocProvider( - create: (_) => AssetsManagerCubit(game, audioPlayer)..load(), - child: PinballGameView(game), - ), + return Scaffold( + backgroundColor: PinballColors.black, + body: BlocProvider( + create: (_) => AssetsManagerCubit(game, audioPlayer)..load(), + child: PinballGameView(game), ), ); } @@ -166,7 +170,7 @@ class _PositionedInfoIcon extends StatelessWidget { visible: state.status.isGameOver, child: IconButton( iconSize: 50, - icon: Assets.images.linkBox.infoIcon.image(), + icon: const Icon(Icons.info, color: PinballColors.white), onPressed: () => showMoreInformationDialog(context), ), ); diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index f0b6fdeb..8d81e5c6 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -14,7 +14,6 @@ class $AssetsImagesGen { const $AssetsImagesBonusAnimationGen(); $AssetsImagesComponentsGen get components => const $AssetsImagesComponentsGen(); - $AssetsImagesLinkBoxGen get linkBox => const $AssetsImagesLinkBoxGen(); $AssetsImagesLoadingGameGen get loadingGame => const $AssetsImagesLoadingGameGen(); $AssetsImagesScoreGen get score => const $AssetsImagesScoreGen(); @@ -56,14 +55,6 @@ 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 $AssetsImagesLoadingGameGen { const $AssetsImagesLoadingGameGen(); diff --git a/lib/how_to_play/widgets/how_to_play_dialog.dart b/lib/how_to_play/widgets/how_to_play_dialog.dart index 8a709605..6c8c3f13 100644 --- a/lib/how_to_play/widgets/how_to_play_dialog.dart +++ b/lib/how_to_play/widgets/how_to_play_dialog.dart @@ -50,14 +50,11 @@ extension on Control { } class HowToPlayDialog extends StatefulWidget { - HowToPlayDialog({ + const HowToPlayDialog({ Key? key, required this.onDismissCallback, - @visibleForTesting PlatformHelper? platformHelper, - }) : platformHelper = platformHelper ?? PlatformHelper(), - super(key: key); + }) : super(key: key); - final PlatformHelper platformHelper; final VoidCallback onDismissCallback; @override @@ -85,7 +82,7 @@ class _HowToPlayDialogState extends State { @override Widget build(BuildContext context) { - final isMobile = widget.platformHelper.isMobile; + final isMobile = context.read().isMobile; final l10n = context.l10n; return WillPopScope( diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8573c585..64093ac6 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -189,7 +189,7 @@ "description": "Text shown on the mobile controls enter button" }, "initialsErrorTitle": "Uh-oh... well, that didn’t work", - "@enter": { + "@initialsErrorTitle": { "description": "Title shown when the initials submission fails" }, "initialsErrorMessage": "Please try a different combination of letters", @@ -200,4 +200,26 @@ "@leaderboardErrorMessage": { "description": "Text shown when the leaderboard had an error while loading" } + , + "letEveryone": "Let everyone know about I/O Pinball", + "@letEveryone": { + "description": "Text displayed on share screen for description" + }, + "bySharingYourScore": "by sharing your score to your preferred", + "@bySharingYourScore": { + "description": "Text displayed on share screen for description" + }, + "socialMediaAccount": "social media account!", + "@socialMediaAccount": { + "description": "Text displayed on share screen for description" + }, + "iGotScoreAtPinball": "I got {score} at the #IOPinball machine, can you beat my score? See you at #GoogleIO!", + "@iGotScoreAtPinball": { + "description": "Text to share score on Social Network", + "placeholders": { + "score": { + "type": "String" + } + } + } } diff --git a/lib/main.dart b/lib/main.dart index 158966c8..877843ee 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,12 +6,17 @@ import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/app/app.dart'; import 'package:pinball/bootstrap.dart'; import 'package:pinball_audio/pinball_audio.dart'; +import 'package:platform_helper/platform_helper.dart'; +import 'package:share_repository/share_repository.dart'; void main() { bootstrap((firestore, firebaseAuth) async { final leaderboardRepository = LeaderboardRepository(firestore); + const shareRepository = + ShareRepository(appUrl: ShareRepository.pinballGameUrl); final authenticationRepository = AuthenticationRepository(firebaseAuth); final pinballAudioPlayer = PinballAudioPlayer(); + final platformHelper = PlatformHelper(); unawaited( Firebase.initializeApp().then( (_) => authenticationRepository.authenticateAnonymously(), @@ -20,7 +25,9 @@ void main() { return App( authenticationRepository: authenticationRepository, leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, pinballAudioPlayer: pinballAudioPlayer, + platformHelper: platformHelper, ); }); } diff --git a/lib/more_information/more_information_dialog.dart b/lib/more_information/more_information_dialog.dart index b9a9ddbb..ca04db4e 100644 --- a/lib/more_information/more_information_dialog.dart +++ b/lib/more_information/more_information_dialog.dart @@ -204,7 +204,7 @@ class _MadeWithFlutterAndFirebase extends StatelessWidget { abstract class _MoreInformationUrl { static const flutterWebsite = 'https://flutter.dev'; static const firebaseWebsite = 'https://firebase.google.com'; - static const openSourceCode = 'https://github.com/VGVentures/pinball'; + static const openSourceCode = 'https://github.com/flutter/pinball'; static const googleIOEvent = 'https://events.google.com/io/'; static const flutterGamesWebsite = 'http://flutter.dev/games'; static const howItsMadeArticle = diff --git a/packages/pinball_audio/assets/sfx/cow_moo.mp3 b/packages/pinball_audio/assets/sfx/cow_moo.mp3 new file mode 100644 index 00000000..ce69e941 Binary files /dev/null and b/packages/pinball_audio/assets/sfx/cow_moo.mp3 differ diff --git a/packages/pinball_audio/assets/sfx/kicker_a.mp3 b/packages/pinball_audio/assets/sfx/kicker_a.mp3 new file mode 100644 index 00000000..475cbc13 Binary files /dev/null and b/packages/pinball_audio/assets/sfx/kicker_a.mp3 differ diff --git a/packages/pinball_audio/assets/sfx/kicker_b.mp3 b/packages/pinball_audio/assets/sfx/kicker_b.mp3 new file mode 100644 index 00000000..2b1bdfbc Binary files /dev/null and b/packages/pinball_audio/assets/sfx/kicker_b.mp3 differ diff --git a/packages/pinball_audio/lib/gen/assets.gen.dart b/packages/pinball_audio/lib/gen/assets.gen.dart index bdd8e09e..c8b66234 100644 --- a/packages/pinball_audio/lib/gen/assets.gen.dart +++ b/packages/pinball_audio/lib/gen/assets.gen.dart @@ -17,11 +17,14 @@ class $AssetsSfxGen { String get android => 'assets/sfx/android.mp3'; String get bumperA => 'assets/sfx/bumper_a.mp3'; String get bumperB => 'assets/sfx/bumper_b.mp3'; + String get cowMoo => 'assets/sfx/cow_moo.mp3'; String get dash => 'assets/sfx/dash.mp3'; String get dino => 'assets/sfx/dino.mp3'; String get gameOverVoiceOver => 'assets/sfx/game_over_voice_over.mp3'; String get google => 'assets/sfx/google.mp3'; String get ioPinballVoiceOver => 'assets/sfx/io_pinball_voice_over.mp3'; + String get kickerA => 'assets/sfx/kicker_a.mp3'; + String get kickerB => 'assets/sfx/kicker_b.mp3'; String get launcher => 'assets/sfx/launcher.mp3'; String get sparky => 'assets/sfx/sparky.mp3'; } diff --git a/packages/pinball_audio/lib/src/pinball_audio.dart b/packages/pinball_audio/lib/src/pinball_audio.dart index 1756d965..9682b520 100644 --- a/packages/pinball_audio/lib/src/pinball_audio.dart +++ b/packages/pinball_audio/lib/src/pinball_audio.dart @@ -1,32 +1,39 @@ import 'dart:math'; import 'package:audioplayers/audioplayers.dart'; +import 'package:clock/clock.dart'; import 'package:flame_audio/audio_pool.dart'; import 'package:flame_audio/flame_audio.dart'; import 'package:flutter/material.dart'; import 'package:pinball_audio/gen/assets.gen.dart'; -/// Sounds available for play +/// Sounds available to play. enum PinballAudio { - /// Google + /// Google. google, - /// Bumper + /// Bumper. bumper, - /// Background music + /// Cow moo. + cowMoo, + + /// Background music. backgroundMusic, - /// IO Pinball voice over + /// IO Pinball voice over. ioPinballVoiceOver, - /// Game over + /// Game over. gameOverVoiceOver, - /// Launcher + /// Launcher. launcher, - /// Sparky + /// Kicker. + kicker, + + /// Sparky. sparky, /// Android @@ -52,7 +59,7 @@ typedef CreateAudioPool = Future Function( typedef PlaySingleAudio = Future Function(String); /// Defines the contract for looping a single audio. -typedef LoopSingleAudio = Future Function(String); +typedef LoopSingleAudio = Future Function(String, {double volume}); /// Defines the contract for pre fetching an audio. typedef PreCacheSingleAudio = Future Function(String); @@ -94,59 +101,120 @@ class _LoopAudio extends _Audio { required this.preCacheSingleAudio, required this.loopSingleAudio, required this.path, + this.volume, }); final PreCacheSingleAudio preCacheSingleAudio; final LoopSingleAudio loopSingleAudio; final String path; + final double? volume; @override Future load() => preCacheSingleAudio(prefixFile(path)); @override void play() { - loopSingleAudio(prefixFile(path)); + loopSingleAudio(prefixFile(path), volume: volume ?? 1); + } +} + +class _SingleLoopAudio extends _LoopAudio { + _SingleLoopAudio({ + required PreCacheSingleAudio preCacheSingleAudio, + required LoopSingleAudio loopSingleAudio, + required String path, + double? volume, + }) : super( + preCacheSingleAudio: preCacheSingleAudio, + loopSingleAudio: loopSingleAudio, + path: path, + volume: volume, + ); + + bool _playing = false; + + @override + void play() { + if (!_playing) { + super.play(); + _playing = true; + } } } -class _BumperAudio extends _Audio { - _BumperAudio({ +class _RandomABAudio extends _Audio { + _RandomABAudio({ required this.createAudioPool, required this.seed, + required this.audioAssetA, + required this.audioAssetB, + this.volume, }); final CreateAudioPool createAudioPool; final Random seed; + final String audioAssetA; + final String audioAssetB; + final double? volume; - late AudioPool bumperA; - late AudioPool bumperB; + late AudioPool audioA; + late AudioPool audioB; @override Future load() async { await Future.wait( [ createAudioPool( - prefixFile(Assets.sfx.bumperA), + prefixFile(audioAssetA), maxPlayers: 4, prefix: '', - ).then((pool) => bumperA = pool), + ).then((pool) => audioA = pool), createAudioPool( - prefixFile(Assets.sfx.bumperB), + prefixFile(audioAssetB), maxPlayers: 4, prefix: '', - ).then((pool) => bumperB = pool), + ).then((pool) => audioB = pool), ], ); } @override void play() { - (seed.nextBool() ? bumperA : bumperB).start(volume: 0.6); + (seed.nextBool() ? audioA : audioB).start(volume: volume ?? 1); + } +} + +class _ThrottledAudio extends _Audio { + _ThrottledAudio({ + required this.preCacheSingleAudio, + required this.playSingleAudio, + required this.path, + required this.duration, + }); + + final PreCacheSingleAudio preCacheSingleAudio; + final PlaySingleAudio playSingleAudio; + final String path; + final Duration duration; + + DateTime? _lastPlayed; + + @override + Future load() => preCacheSingleAudio(prefixFile(path)); + + @override + void play() { + final now = clock.now(); + if (_lastPlayed == null || + (_lastPlayed != null && now.difference(_lastPlayed!) > duration)) { + _lastPlayed = now; + playSingleAudio(prefixFile(path)); + } } } /// {@template pinball_audio_player} -/// Sound manager for the pinball game +/// Sound manager for the pinball game. /// {@endtemplate} class PinballAudioPlayer { /// {@macro pinball_audio_player} @@ -208,14 +276,31 @@ class PinballAudioPlayer { playSingleAudio: _playSingleAudio, path: Assets.sfx.gameOverVoiceOver, ), - PinballAudio.bumper: _BumperAudio( + PinballAudio.bumper: _RandomABAudio( + createAudioPool: _createAudioPool, + seed: _seed, + audioAssetA: Assets.sfx.bumperA, + audioAssetB: Assets.sfx.bumperB, + volume: 0.6, + ), + PinballAudio.kicker: _RandomABAudio( createAudioPool: _createAudioPool, seed: _seed, + audioAssetA: Assets.sfx.kickerA, + audioAssetB: Assets.sfx.kickerB, + volume: 0.6, + ), + PinballAudio.cowMoo: _ThrottledAudio( + preCacheSingleAudio: _preCacheSingleAudio, + playSingleAudio: _playSingleAudio, + path: Assets.sfx.cowMoo, + duration: const Duration(seconds: 2), ), - PinballAudio.backgroundMusic: _LoopAudio( + PinballAudio.backgroundMusic: _SingleLoopAudio( preCacheSingleAudio: _preCacheSingleAudio, loopSingleAudio: _loopSingleAudio, path: Assets.music.background, + volume: .6, ), }; } @@ -232,19 +317,19 @@ class PinballAudioPlayer { final Random _seed; - /// Registered audios on the Player + /// Registered audios on the Player. @visibleForTesting // ignore: library_private_types_in_public_api late final Map audios; - /// Loads the sounds effects into the memory + /// Loads the sounds effects into the memory. List> load() { _configureAudioCache(FlameAudio.audioCache); return audios.values.map((a) => a.load()).toList(); } - /// Plays the received audio + /// Plays the received audio. void play(PinballAudio audio) { assert( audios.containsKey(audio), diff --git a/packages/pinball_audio/pubspec.yaml b/packages/pinball_audio/pubspec.yaml index 74713dfa..8c99d1fc 100644 --- a/packages/pinball_audio/pubspec.yaml +++ b/packages/pinball_audio/pubspec.yaml @@ -8,6 +8,7 @@ environment: dependencies: audioplayers: ^0.20.1 + clock: ^1.1.0 flame_audio: ^1.0.1 flutter: sdk: flutter diff --git a/packages/pinball_audio/test/src/pinball_audio_test.dart b/packages/pinball_audio/test/src/pinball_audio_test.dart index 8374e820..3e147329 100644 --- a/packages/pinball_audio/test/src/pinball_audio_test.dart +++ b/packages/pinball_audio/test/src/pinball_audio_test.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:audioplayers/audioplayers.dart'; +import 'package:clock/clock.dart'; import 'package:flame_audio/audio_pool.dart'; import 'package:flame_audio/flame_audio.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -32,7 +33,7 @@ class _MockPlaySingleAudio extends Mock { } class _MockLoopSingleAudio extends Mock { - Future onCall(String url); + Future onCall(String url, {double volume}); } abstract class _PreCacheSingleAudio { @@ -43,6 +44,8 @@ class _MockPreCacheSingleAudio extends Mock implements _PreCacheSingleAudio {} class _MockRandom extends Mock implements Random {} +class _MockClock extends Mock implements Clock {} + void main() { group('PinballAudio', () { late _MockCreateAudioPool createAudioPool; @@ -74,7 +77,8 @@ void main() { when(() => playSingleAudio.onCall(any())).thenAnswer((_) async {}); loopSingleAudio = _MockLoopSingleAudio(); - when(() => loopSingleAudio.onCall(any())).thenAnswer((_) async {}); + when(() => loopSingleAudio.onCall(any(), volume: any(named: 'volume'))) + .thenAnswer((_) async {}); preCacheSingleAudio = _MockPreCacheSingleAudio(); when(() => preCacheSingleAudio.onCall(any())).thenAnswer((_) async {}); @@ -116,6 +120,26 @@ void main() { ).called(1); }); + test('creates the kicker pools', () async { + await Future.wait(audioPlayer.load()); + + verify( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerA}', + maxPlayers: 4, + prefix: '', + ), + ).called(1); + + verify( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerB}', + maxPlayers: 4, + prefix: '', + ), + ).called(1); + }); + test('configures the audio cache instance', () async { await Future.wait(audioPlayer.load()); @@ -171,6 +195,10 @@ void main() { () => preCacheSingleAudio .onCall('packages/pinball_audio/assets/sfx/launcher.mp3'), ).called(1); + verify( + () => preCacheSingleAudio + .onCall('packages/pinball_audio/assets/sfx/cow_moo.mp3'), + ).called(1); verify( () => preCacheSingleAudio .onCall('packages/pinball_audio/assets/music/background.mp3'), @@ -227,6 +255,91 @@ void main() { }); }); + group('kicker', () { + late AudioPool kickerAPool; + late AudioPool kickerBPool; + + setUp(() { + kickerAPool = _MockAudioPool(); + when(() => kickerAPool.start(volume: any(named: 'volume'))) + .thenAnswer((_) async => () {}); + when( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerA}', + maxPlayers: any(named: 'maxPlayers'), + prefix: any(named: 'prefix'), + ), + ).thenAnswer((_) async => kickerAPool); + + kickerBPool = _MockAudioPool(); + when(() => kickerBPool.start(volume: any(named: 'volume'))) + .thenAnswer((_) async => () {}); + when( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerB}', + maxPlayers: any(named: 'maxPlayers'), + prefix: any(named: 'prefix'), + ), + ).thenAnswer((_) async => kickerBPool); + }); + + group('when seed is true', () { + test('plays the kicker A sound pool', () async { + when(seed.nextBool).thenReturn(true); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.kicker); + + verify(() => kickerAPool.start(volume: 0.6)).called(1); + }); + }); + + group('when seed is false', () { + test('plays the kicker B sound pool', () async { + when(seed.nextBool).thenReturn(false); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.kicker); + + verify(() => kickerBPool.start(volume: 0.6)).called(1); + }); + }); + }); + + group('cow moo', () { + test('plays the correct file', () async { + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.cowMoo); + + verify( + () => playSingleAudio + .onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'), + ).called(1); + }); + + test('only plays the sound again after 2 seconds', () async { + final clock = _MockClock(); + await withClock(clock, () async { + when(clock.now).thenReturn(DateTime(2022)); + await Future.wait(audioPlayer.load()); + audioPlayer + ..play(PinballAudio.cowMoo) + ..play(PinballAudio.cowMoo); + + verify( + () => playSingleAudio + .onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'), + ).called(1); + + when(clock.now).thenReturn(DateTime(2022, 1, 1, 1, 2)); + audioPlayer.play(PinballAudio.cowMoo); + + verify( + () => playSingleAudio + .onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'), + ).called(1); + }); + }); + }); + group('google', () { test('plays the correct file', () async { await Future.wait(audioPlayer.load()); @@ -331,8 +444,24 @@ void main() { audioPlayer.play(PinballAudio.backgroundMusic); verify( - () => loopSingleAudio - .onCall('packages/pinball_audio/${Assets.music.background}'), + () => loopSingleAudio.onCall( + 'packages/pinball_audio/${Assets.music.background}', + volume: .6, + ), + ).called(1); + }); + + test('plays only once', () async { + await Future.wait(audioPlayer.load()); + audioPlayer + ..play(PinballAudio.backgroundMusic) + ..play(PinballAudio.backgroundMusic); + + verify( + () => loopSingleAudio.onCall( + 'packages/pinball_audio/${Assets.music.background}', + volume: .6, + ), ).called(1); }); }); diff --git a/packages/pinball_components/assets/images/android/ramp/board-opening.png b/packages/pinball_components/assets/images/android/ramp/board_opening.png similarity index 100% rename from packages/pinball_components/assets/images/android/ramp/board-opening.png rename to packages/pinball_components/assets/images/android/ramp/board_opening.png diff --git a/packages/pinball_components/assets/images/android/ramp/railing-background.png b/packages/pinball_components/assets/images/android/ramp/railing_background.png similarity index 100% rename from packages/pinball_components/assets/images/android/ramp/railing-background.png rename to packages/pinball_components/assets/images/android/ramp/railing_background.png diff --git a/packages/pinball_components/assets/images/android/ramp/railing-foreground.png b/packages/pinball_components/assets/images/android/ramp/railing_foreground.png similarity index 100% rename from packages/pinball_components/assets/images/android/ramp/railing-foreground.png rename to packages/pinball_components/assets/images/android/ramp/railing_foreground.png diff --git a/packages/pinball_components/assets/images/android/spaceship/light-beam.png b/packages/pinball_components/assets/images/android/spaceship/light_beam.png similarity index 100% rename from packages/pinball_components/assets/images/android/spaceship/light-beam.png rename to packages/pinball_components/assets/images/android/spaceship/light_beam.png diff --git a/packages/pinball_components/assets/images/backbox/button/facebook.png b/packages/pinball_components/assets/images/backbox/button/facebook.png new file mode 100644 index 00000000..f6d29ab2 Binary files /dev/null and b/packages/pinball_components/assets/images/backbox/button/facebook.png differ diff --git a/packages/pinball_components/assets/images/backbox/button/twitter.png b/packages/pinball_components/assets/images/backbox/button/twitter.png new file mode 100644 index 00000000..f109a4b8 Binary files /dev/null and b/packages/pinball_components/assets/images/backbox/button/twitter.png differ diff --git a/packages/pinball_components/assets/images/backbox/display-divider.png b/packages/pinball_components/assets/images/backbox/display_divider.png similarity index 100% rename from packages/pinball_components/assets/images/backbox/display-divider.png rename to packages/pinball_components/assets/images/backbox/display_divider.png diff --git a/packages/pinball_components/assets/images/backbox/marquee.png b/packages/pinball_components/assets/images/backbox/marquee.png index ee98a495..8603c9b8 100644 Binary files a/packages/pinball_components/assets/images/backbox/marquee.png and b/packages/pinball_components/assets/images/backbox/marquee.png differ diff --git a/packages/pinball_components/assets/images/ball/flame_effect.png b/packages/pinball_components/assets/images/ball/flame_effect.png index 03a6fca6..b1397f36 100644 Binary files a/packages/pinball_components/assets/images/ball/flame_effect.png and b/packages/pinball_components/assets/images/ball/flame_effect.png differ diff --git a/packages/pinball_components/assets/images/baseboard/left.png b/packages/pinball_components/assets/images/baseboard/left.png index d13b4e31..552c6e1d 100644 Binary files a/packages/pinball_components/assets/images/baseboard/left.png and b/packages/pinball_components/assets/images/baseboard/left.png differ diff --git a/packages/pinball_components/assets/images/baseboard/right.png b/packages/pinball_components/assets/images/baseboard/right.png index 8ad93045..9088d7e0 100644 Binary files a/packages/pinball_components/assets/images/baseboard/right.png and b/packages/pinball_components/assets/images/baseboard/right.png differ diff --git a/packages/pinball_components/assets/images/board-background.png b/packages/pinball_components/assets/images/board-background.png deleted file mode 100644 index dabf9026..00000000 Binary files a/packages/pinball_components/assets/images/board-background.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/board_background.png b/packages/pinball_components/assets/images/board_background.png new file mode 100644 index 00000000..41af91ec Binary files /dev/null and b/packages/pinball_components/assets/images/board_background.png differ diff --git a/packages/pinball_components/assets/images/boundary/bottom.png b/packages/pinball_components/assets/images/boundary/bottom.png index 523e6156..a9e5785b 100644 Binary files a/packages/pinball_components/assets/images/boundary/bottom.png and b/packages/pinball_components/assets/images/boundary/bottom.png differ diff --git a/packages/pinball_components/assets/images/boundary/outer-bottom.png b/packages/pinball_components/assets/images/boundary/outer-bottom.png deleted file mode 100644 index 508bcee8..00000000 Binary files a/packages/pinball_components/assets/images/boundary/outer-bottom.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/boundary/outer.png b/packages/pinball_components/assets/images/boundary/outer.png index 75ccdb6b..d7bc719e 100644 Binary files a/packages/pinball_components/assets/images/boundary/outer.png and b/packages/pinball_components/assets/images/boundary/outer.png differ diff --git a/packages/pinball_components/assets/images/boundary/outer_bottom.png b/packages/pinball_components/assets/images/boundary/outer_bottom.png new file mode 100644 index 00000000..9f5cd308 Binary files /dev/null and b/packages/pinball_components/assets/images/boundary/outer_bottom.png differ diff --git a/packages/pinball_components/assets/images/dash/animatronic.png b/packages/pinball_components/assets/images/dash/animatronic.png index 13f7b794..d2aa19c5 100644 Binary files a/packages/pinball_components/assets/images/dash/animatronic.png and b/packages/pinball_components/assets/images/dash/animatronic.png differ diff --git a/packages/pinball_components/assets/images/dino/animatronic/head.png b/packages/pinball_components/assets/images/dino/animatronic/head.png index 87332679..44ce80f2 100644 Binary files a/packages/pinball_components/assets/images/dino/animatronic/head.png and b/packages/pinball_components/assets/images/dino/animatronic/head.png differ diff --git a/packages/pinball_components/assets/images/dino/animatronic/mouth.png b/packages/pinball_components/assets/images/dino/animatronic/mouth.png index 4955bdf3..98b1386b 100644 Binary files a/packages/pinball_components/assets/images/dino/animatronic/mouth.png and b/packages/pinball_components/assets/images/dino/animatronic/mouth.png differ diff --git a/packages/pinball_components/assets/images/dino/bottom-wall.png b/packages/pinball_components/assets/images/dino/bottom_wall.png similarity index 100% rename from packages/pinball_components/assets/images/dino/bottom-wall.png rename to packages/pinball_components/assets/images/dino/bottom_wall.png diff --git a/packages/pinball_components/assets/images/dino/top-wall.png b/packages/pinball_components/assets/images/dino/top_wall.png similarity index 100% rename from packages/pinball_components/assets/images/dino/top-wall.png rename to packages/pinball_components/assets/images/dino/top_wall.png diff --git a/packages/pinball_components/assets/images/dino/top-wall-tunnel.png b/packages/pinball_components/assets/images/dino/top_wall_tunnel.png similarity index 100% rename from packages/pinball_components/assets/images/dino/top-wall-tunnel.png rename to packages/pinball_components/assets/images/dino/top_wall_tunnel.png diff --git a/packages/pinball_components/assets/images/display_arrows/arrow_left.png b/packages/pinball_components/assets/images/display_arrows/arrow_left.png new file mode 100644 index 00000000..e851bef4 Binary files /dev/null and b/packages/pinball_components/assets/images/display_arrows/arrow_left.png differ diff --git a/packages/pinball_components/assets/images/display_arrows/arrow_right.png b/packages/pinball_components/assets/images/display_arrows/arrow_right.png new file mode 100644 index 00000000..c8d5d2f2 Binary files /dev/null and b/packages/pinball_components/assets/images/display_arrows/arrow_right.png differ diff --git a/packages/pinball_components/assets/images/error_background.png b/packages/pinball_components/assets/images/error_background.png index 5aa6595f..73e934ca 100644 Binary files a/packages/pinball_components/assets/images/error_background.png and b/packages/pinball_components/assets/images/error_background.png differ diff --git a/packages/pinball_components/assets/images/flapper/back-support.png b/packages/pinball_components/assets/images/flapper/back_support.png similarity index 100% rename from packages/pinball_components/assets/images/flapper/back-support.png rename to packages/pinball_components/assets/images/flapper/back_support.png diff --git a/packages/pinball_components/assets/images/flapper/front-support.png b/packages/pinball_components/assets/images/flapper/front_support.png similarity index 100% rename from packages/pinball_components/assets/images/flapper/front-support.png rename to packages/pinball_components/assets/images/flapper/front_support.png diff --git a/packages/pinball_components/assets/images/google_rollover/left/decal.png b/packages/pinball_components/assets/images/google_rollover/left/decal.png new file mode 100644 index 00000000..da503131 Binary files /dev/null and b/packages/pinball_components/assets/images/google_rollover/left/decal.png differ diff --git a/packages/pinball_components/assets/images/google_rollover/left/pin.png b/packages/pinball_components/assets/images/google_rollover/left/pin.png new file mode 100644 index 00000000..50a95a6b Binary files /dev/null and b/packages/pinball_components/assets/images/google_rollover/left/pin.png differ diff --git a/packages/pinball_components/assets/images/google_rollover/right/decal.png b/packages/pinball_components/assets/images/google_rollover/right/decal.png new file mode 100644 index 00000000..68496caf Binary files /dev/null and b/packages/pinball_components/assets/images/google_rollover/right/decal.png differ diff --git a/packages/pinball_components/assets/images/google_rollover/right/pin.png b/packages/pinball_components/assets/images/google_rollover/right/pin.png new file mode 100644 index 00000000..19bba084 Binary files /dev/null and b/packages/pinball_components/assets/images/google_rollover/right/pin.png differ diff --git a/packages/pinball_components/assets/images/launch_ramp/background-railing.png b/packages/pinball_components/assets/images/launch_ramp/background-railing.png deleted file mode 100644 index aa7d5774..00000000 Binary files a/packages/pinball_components/assets/images/launch_ramp/background-railing.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/launch_ramp/background_railing.png b/packages/pinball_components/assets/images/launch_ramp/background_railing.png new file mode 100644 index 00000000..e9201957 Binary files /dev/null and b/packages/pinball_components/assets/images/launch_ramp/background_railing.png differ diff --git a/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png b/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png deleted file mode 100644 index f953fdf5..00000000 Binary files a/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/launch_ramp/foreground_railing.png b/packages/pinball_components/assets/images/launch_ramp/foreground_railing.png new file mode 100644 index 00000000..1a48f663 Binary files /dev/null and b/packages/pinball_components/assets/images/launch_ramp/foreground_railing.png differ diff --git a/packages/pinball_components/assets/images/launch_ramp/ramp.png b/packages/pinball_components/assets/images/launch_ramp/ramp.png index 61481f5a..6b2edde1 100644 Binary files a/packages/pinball_components/assets/images/launch_ramp/ramp.png and b/packages/pinball_components/assets/images/launch_ramp/ramp.png differ diff --git a/packages/pinball_components/assets/images/plunger/plunger.png b/packages/pinball_components/assets/images/plunger/plunger.png index 2ec6e001..df5812cd 100644 Binary files a/packages/pinball_components/assets/images/plunger/plunger.png and b/packages/pinball_components/assets/images/plunger/plunger.png differ diff --git a/packages/pinball_components/assets/images/plunger/rocket.png b/packages/pinball_components/assets/images/plunger/rocket.png index a8f89152..ef2df7e5 100644 Binary files a/packages/pinball_components/assets/images/plunger/rocket.png and b/packages/pinball_components/assets/images/plunger/rocket.png differ diff --git a/packages/pinball_components/assets/images/score/five-thousand.png b/packages/pinball_components/assets/images/score/five-thousand.png deleted file mode 100644 index d373e2e1..00000000 Binary files a/packages/pinball_components/assets/images/score/five-thousand.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/score/five_thousand.png b/packages/pinball_components/assets/images/score/five_thousand.png new file mode 100644 index 00000000..a2c37dec Binary files /dev/null and b/packages/pinball_components/assets/images/score/five_thousand.png differ diff --git a/packages/pinball_components/assets/images/score/one-million.png b/packages/pinball_components/assets/images/score/one-million.png deleted file mode 100644 index 5c7ec15b..00000000 Binary files a/packages/pinball_components/assets/images/score/one-million.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/score/one_million.png b/packages/pinball_components/assets/images/score/one_million.png new file mode 100644 index 00000000..fcbd4919 Binary files /dev/null and b/packages/pinball_components/assets/images/score/one_million.png differ diff --git a/packages/pinball_components/assets/images/score/twenty-thousand.png b/packages/pinball_components/assets/images/score/twenty-thousand.png deleted file mode 100644 index 2f9bfd57..00000000 Binary files a/packages/pinball_components/assets/images/score/twenty-thousand.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/score/twenty_thousand.png b/packages/pinball_components/assets/images/score/twenty_thousand.png new file mode 100644 index 00000000..cebdf3b5 Binary files /dev/null and b/packages/pinball_components/assets/images/score/twenty_thousand.png differ diff --git a/packages/pinball_components/assets/images/score/two-hundred-thousand.png b/packages/pinball_components/assets/images/score/two-hundred-thousand.png deleted file mode 100644 index a6f19db4..00000000 Binary files a/packages/pinball_components/assets/images/score/two-hundred-thousand.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/score/two_hundred_thousand.png b/packages/pinball_components/assets/images/score/two_hundred_thousand.png new file mode 100644 index 00000000..672b18a0 Binary files /dev/null and b/packages/pinball_components/assets/images/score/two_hundred_thousand.png differ diff --git a/packages/pinball_components/assets/images/signpost/active1.png b/packages/pinball_components/assets/images/signpost/active1.png index 78997bf6..23f5836f 100644 Binary files a/packages/pinball_components/assets/images/signpost/active1.png and b/packages/pinball_components/assets/images/signpost/active1.png differ diff --git a/packages/pinball_components/assets/images/signpost/active2.png b/packages/pinball_components/assets/images/signpost/active2.png index 39caa821..6f79aa47 100644 Binary files a/packages/pinball_components/assets/images/signpost/active2.png and b/packages/pinball_components/assets/images/signpost/active2.png differ diff --git a/packages/pinball_components/assets/images/signpost/active3.png b/packages/pinball_components/assets/images/signpost/active3.png index f43c190c..7d54547a 100644 Binary files a/packages/pinball_components/assets/images/signpost/active3.png and b/packages/pinball_components/assets/images/signpost/active3.png differ diff --git a/packages/pinball_components/assets/images/signpost/inactive.png b/packages/pinball_components/assets/images/signpost/inactive.png index 9fa23330..f0721f3c 100644 Binary files a/packages/pinball_components/assets/images/signpost/inactive.png and b/packages/pinball_components/assets/images/signpost/inactive.png differ diff --git a/packages/pinball_components/assets/images/skill_shot/decal.png b/packages/pinball_components/assets/images/skill_shot/decal.png index 120d70aa..02fbd5cf 100644 Binary files a/packages/pinball_components/assets/images/skill_shot/decal.png and b/packages/pinball_components/assets/images/skill_shot/decal.png differ diff --git a/packages/pinball_components/assets/images/skill_shot/dimmed.png b/packages/pinball_components/assets/images/skill_shot/dimmed.png index 7cc32bd4..4e3a5ca9 100644 Binary files a/packages/pinball_components/assets/images/skill_shot/dimmed.png and b/packages/pinball_components/assets/images/skill_shot/dimmed.png differ diff --git a/packages/pinball_components/assets/images/skill_shot/lit.png b/packages/pinball_components/assets/images/skill_shot/lit.png index d1bce99b..cc825171 100644 Binary files a/packages/pinball_components/assets/images/skill_shot/lit.png and b/packages/pinball_components/assets/images/skill_shot/lit.png differ diff --git a/packages/pinball_components/assets/images/skill_shot/pin.png b/packages/pinball_components/assets/images/skill_shot/pin.png index 5b64e1ab..61126586 100644 Binary files a/packages/pinball_components/assets/images/skill_shot/pin.png and b/packages/pinball_components/assets/images/skill_shot/pin.png differ diff --git a/packages/pinball_components/assets/images/sparky/animatronic.png b/packages/pinball_components/assets/images/sparky/animatronic.png index cc57e405..a19c3dac 100644 Binary files a/packages/pinball_components/assets/images/sparky/animatronic.png and b/packages/pinball_components/assets/images/sparky/animatronic.png differ diff --git a/packages/pinball_components/assets/images/sparky/computer/base.png b/packages/pinball_components/assets/images/sparky/computer/base.png index 188e4329..9abd60d8 100644 Binary files a/packages/pinball_components/assets/images/sparky/computer/base.png and b/packages/pinball_components/assets/images/sparky/computer/base.png differ diff --git a/packages/pinball_components/assets/images/sparky/computer/glow.png b/packages/pinball_components/assets/images/sparky/computer/glow.png index 7fd9a0c8..8190d131 100644 Binary files a/packages/pinball_components/assets/images/sparky/computer/glow.png and b/packages/pinball_components/assets/images/sparky/computer/glow.png differ diff --git a/packages/pinball_components/assets/images/sparky/computer/top.png b/packages/pinball_components/assets/images/sparky/computer/top.png index 085771cd..445a8027 100644 Binary files a/packages/pinball_components/assets/images/sparky/computer/top.png and b/packages/pinball_components/assets/images/sparky/computer/top.png differ diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index 0288d3f4..19b1571a 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -15,20 +15,25 @@ class $AssetsImagesGen { $AssetsImagesBallGen get ball => const $AssetsImagesBallGen(); $AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen(); - /// File path: assets/images/board-background.png + /// File path: assets/images/board_background.png AssetGenImage get boardBackground => - const AssetGenImage('assets/images/board-background.png'); + const AssetGenImage('assets/images/board_background.png'); $AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen(); $AssetsImagesDashGen get dash => const $AssetsImagesDashGen(); $AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen(); + $AssetsImagesDisplayArrowsGen get displayArrows => + const $AssetsImagesDisplayArrowsGen(); + /// 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(); + $AssetsImagesGoogleRolloverGen get googleRollover => + const $AssetsImagesGoogleRolloverGen(); $AssetsImagesGoogleWordGen get googleWord => const $AssetsImagesGoogleWordGen(); $AssetsImagesKickerGen get kicker => const $AssetsImagesKickerGen(); @@ -59,9 +64,12 @@ class $AssetsImagesAndroidGen { class $AssetsImagesBackboxGen { const $AssetsImagesBackboxGen(); - /// File path: assets/images/backbox/display-divider.png + $AssetsImagesBackboxButtonGen get button => + const $AssetsImagesBackboxButtonGen(); + + /// File path: assets/images/backbox/display_divider.png AssetGenImage get displayDivider => - const AssetGenImage('assets/images/backbox/display-divider.png'); + const AssetGenImage('assets/images/backbox/display_divider.png'); /// File path: assets/images/backbox/display_title_decoration.png AssetGenImage get displayTitleDecoration => @@ -99,13 +107,13 @@ class $AssetsImagesBoundaryGen { 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'); + + /// File path: assets/images/boundary/outer_bottom.png + AssetGenImage get outerBottom => + const AssetGenImage('assets/images/boundary/outer_bottom.png'); } class $AssetsImagesDashGen { @@ -124,33 +132,42 @@ class $AssetsImagesDinoGen { $AssetsImagesDinoAnimatronicGen get animatronic => const $AssetsImagesDinoAnimatronicGen(); - /// File path: assets/images/dino/bottom-wall.png + /// File path: assets/images/dino/bottom_wall.png AssetGenImage get bottomWall => - const AssetGenImage('assets/images/dino/bottom-wall.png'); + const AssetGenImage('assets/images/dino/bottom_wall.png'); + + /// File path: assets/images/dino/top_wall.png + AssetGenImage get topWall => + const AssetGenImage('assets/images/dino/top_wall.png'); - /// File path: assets/images/dino/top-wall-tunnel.png + /// File path: assets/images/dino/top_wall_tunnel.png AssetGenImage get topWallTunnel => - const AssetGenImage('assets/images/dino/top-wall-tunnel.png'); + const AssetGenImage('assets/images/dino/top_wall_tunnel.png'); +} - /// File path: assets/images/dino/top-wall.png - AssetGenImage get topWall => - const AssetGenImage('assets/images/dino/top-wall.png'); +class $AssetsImagesDisplayArrowsGen { + const $AssetsImagesDisplayArrowsGen(); + + AssetGenImage get arrowLeft => + const AssetGenImage('assets/images/display_arrows/arrow_left.png'); + AssetGenImage get arrowRight => + const AssetGenImage('assets/images/display_arrows/arrow_right.png'); } class $AssetsImagesFlapperGen { const $AssetsImagesFlapperGen(); - /// File path: assets/images/flapper/back-support.png + /// File path: assets/images/flapper/back_support.png AssetGenImage get backSupport => - const AssetGenImage('assets/images/flapper/back-support.png'); + const AssetGenImage('assets/images/flapper/back_support.png'); /// File path: assets/images/flapper/flap.png AssetGenImage get flap => const AssetGenImage('assets/images/flapper/flap.png'); - /// File path: assets/images/flapper/front-support.png + /// File path: assets/images/flapper/front_support.png AssetGenImage get frontSupport => - const AssetGenImage('assets/images/flapper/front-support.png'); + const AssetGenImage('assets/images/flapper/front_support.png'); } class $AssetsImagesFlipperGen { @@ -165,6 +182,15 @@ class $AssetsImagesFlipperGen { const AssetGenImage('assets/images/flipper/right.png'); } +class $AssetsImagesGoogleRolloverGen { + const $AssetsImagesGoogleRolloverGen(); + + $AssetsImagesGoogleRolloverLeftGen get left => + const $AssetsImagesGoogleRolloverLeftGen(); + $AssetsImagesGoogleRolloverRightGen get right => + const $AssetsImagesGoogleRolloverRightGen(); +} + class $AssetsImagesGoogleWordGen { const $AssetsImagesGoogleWordGen(); @@ -192,13 +218,13 @@ class $AssetsImagesKickerGen { class $AssetsImagesLaunchRampGen { const $AssetsImagesLaunchRampGen(); - /// File path: assets/images/launch_ramp/background-railing.png + /// File path: assets/images/launch_ramp/background_railing.png AssetGenImage get backgroundRailing => - const AssetGenImage('assets/images/launch_ramp/background-railing.png'); + const AssetGenImage('assets/images/launch_ramp/background_railing.png'); - /// File path: assets/images/launch_ramp/foreground-railing.png + /// File path: assets/images/launch_ramp/foreground_railing.png AssetGenImage get foregroundRailing => - const AssetGenImage('assets/images/launch_ramp/foreground-railing.png'); + const AssetGenImage('assets/images/launch_ramp/foreground_railing.png'); /// File path: assets/images/launch_ramp/ramp.png AssetGenImage get ramp => @@ -242,21 +268,21 @@ class $AssetsImagesPlungerGen { class $AssetsImagesScoreGen { const $AssetsImagesScoreGen(); - /// File path: assets/images/score/five-thousand.png + /// File path: assets/images/score/five_thousand.png AssetGenImage get fiveThousand => - const AssetGenImage('assets/images/score/five-thousand.png'); + const AssetGenImage('assets/images/score/five_thousand.png'); - /// File path: assets/images/score/one-million.png + /// File path: assets/images/score/one_million.png AssetGenImage get oneMillion => - const AssetGenImage('assets/images/score/one-million.png'); + const AssetGenImage('assets/images/score/one_million.png'); - /// File path: assets/images/score/twenty-thousand.png + /// File path: assets/images/score/twenty_thousand.png AssetGenImage get twentyThousand => - const AssetGenImage('assets/images/score/twenty-thousand.png'); + const AssetGenImage('assets/images/score/twenty_thousand.png'); - /// File path: assets/images/score/two-hundred-thousand.png + /// File path: assets/images/score/two_hundred_thousand.png AssetGenImage get twoHundredThousand => - const AssetGenImage('assets/images/score/two-hundred-thousand.png'); + const AssetGenImage('assets/images/score/two_hundred_thousand.png'); } class $AssetsImagesSignpostGen { @@ -353,21 +379,21 @@ class $AssetsImagesAndroidRampGen { $AssetsImagesAndroidRampArrowGen get arrow => const $AssetsImagesAndroidRampArrowGen(); - /// File path: assets/images/android/ramp/board-opening.png + /// File path: assets/images/android/ramp/board_opening.png AssetGenImage get boardOpening => - const AssetGenImage('assets/images/android/ramp/board-opening.png'); + const AssetGenImage('assets/images/android/ramp/board_opening.png'); /// File path: assets/images/android/ramp/main.png AssetGenImage get main => const AssetGenImage('assets/images/android/ramp/main.png'); - /// File path: assets/images/android/ramp/railing-background.png + /// File path: assets/images/android/ramp/railing_background.png AssetGenImage get railingBackground => - const AssetGenImage('assets/images/android/ramp/railing-background.png'); + const AssetGenImage('assets/images/android/ramp/railing_background.png'); - /// File path: assets/images/android/ramp/railing-foreground.png + /// File path: assets/images/android/ramp/railing_foreground.png AssetGenImage get railingForeground => - const AssetGenImage('assets/images/android/ramp/railing-foreground.png'); + const AssetGenImage('assets/images/android/ramp/railing_foreground.png'); } class $AssetsImagesAndroidSpaceshipGen { @@ -377,15 +403,27 @@ class $AssetsImagesAndroidSpaceshipGen { AssetGenImage get animatronic => const AssetGenImage('assets/images/android/spaceship/animatronic.png'); - /// File path: assets/images/android/spaceship/light-beam.png + /// File path: assets/images/android/spaceship/light_beam.png AssetGenImage get lightBeam => - const AssetGenImage('assets/images/android/spaceship/light-beam.png'); + const AssetGenImage('assets/images/android/spaceship/light_beam.png'); /// File path: assets/images/android/spaceship/saucer.png AssetGenImage get saucer => const AssetGenImage('assets/images/android/spaceship/saucer.png'); } +class $AssetsImagesBackboxButtonGen { + const $AssetsImagesBackboxButtonGen(); + + /// File path: assets/images/backbox/button/facebook.png + AssetGenImage get facebook => + const AssetGenImage('assets/images/backbox/button/facebook.png'); + + /// File path: assets/images/backbox/button/twitter.png + AssetGenImage get twitter => + const AssetGenImage('assets/images/backbox/button/twitter.png'); +} + class $AssetsImagesDashBumperGen { const $AssetsImagesDashBumperGen(); @@ -407,6 +445,24 @@ class $AssetsImagesDinoAnimatronicGen { const AssetGenImage('assets/images/dino/animatronic/mouth.png'); } +class $AssetsImagesGoogleRolloverLeftGen { + const $AssetsImagesGoogleRolloverLeftGen(); + + AssetGenImage get decal => + const AssetGenImage('assets/images/google_rollover/left/decal.png'); + AssetGenImage get pin => + const AssetGenImage('assets/images/google_rollover/left/pin.png'); +} + +class $AssetsImagesGoogleRolloverRightGen { + const $AssetsImagesGoogleRolloverRightGen(); + + AssetGenImage get decal => + const AssetGenImage('assets/images/google_rollover/right/decal.png'); + AssetGenImage get pin => + const AssetGenImage('assets/images/google_rollover/right/pin.png'); +} + class $AssetsImagesGoogleWordLetter1Gen { const $AssetsImagesGoogleWordLetter1Gen(); diff --git a/packages/pinball_components/lib/src/components/arcade_background/arcade_background.dart b/packages/pinball_components/lib/src/components/arcade_background/arcade_background.dart new file mode 100644 index 00000000..e9936367 --- /dev/null +++ b/packages/pinball_components/lib/src/components/arcade_background/arcade_background.dart @@ -0,0 +1,92 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:pinball_theme/pinball_theme.dart' as theme; + +export 'cubit/arcade_background_cubit.dart'; + +/// {@template arcade_background} +/// Background of the arcade that the pinball machine lives in. +/// {@endtemplate} +class ArcadeBackground extends Component with ZIndex { + /// {@macro arcade_background} + ArcadeBackground({String? assetPath}) + : this._( + bloc: ArcadeBackgroundCubit(), + assetPath: assetPath, + ); + + ArcadeBackground._({required this.bloc, String? assetPath}) + : super( + children: [ + FlameBlocProvider.value( + value: bloc, + children: [ArcadeBackgroundSpriteComponent(assetPath: assetPath)], + ) + ], + ) { + zIndex = ZIndexes.arcadeBackground; + } + + /// Creates an [ArcadeBackground] without any behaviors. + /// + /// This can be used for testing [ArcadeBackground]'s behaviors in isolation. + @visibleForTesting + ArcadeBackground.test({ + ArcadeBackgroundCubit? bloc, + String? assetPath, + }) : bloc = bloc ?? ArcadeBackgroundCubit(), + super( + children: [ + FlameBlocProvider.value( + value: bloc ?? ArcadeBackgroundCubit(), + children: [ArcadeBackgroundSpriteComponent(assetPath: assetPath)], + ) + ], + ); + + /// Bloc to update the arcade background sprite when a new character is + /// selected. + final ArcadeBackgroundCubit bloc; +} + +/// {@template arcade_background_sprite_component} +/// [SpriteComponent] for the [ArcadeBackground]. +/// {@endtemplate} +@visibleForTesting +class ArcadeBackgroundSpriteComponent extends SpriteComponent + with + FlameBlocListenable, + HasGameRef { + /// {@macro arcade_background_sprite_component} + ArcadeBackgroundSpriteComponent({required String? assetPath}) + : _assetPath = assetPath, + super( + anchor: Anchor.bottomCenter, + position: Vector2(0, 72.3), + ); + + final String? _assetPath; + + @override + void onNewState(ArcadeBackgroundState state) { + sprite = Sprite( + gameRef.images.fromCache(state.characterTheme.background.keyName), + ); + } + + @override + Future onLoad() async { + await super.onLoad(); + final sprite = Sprite( + gameRef.images + .fromCache(_assetPath ?? theme.Assets.images.dash.background.keyName), + ); + this.sprite = sprite; + size = sprite.originalSize / 10; + } +} diff --git a/packages/pinball_components/lib/src/components/arcade_background/cubit/arcade_background_cubit.dart b/packages/pinball_components/lib/src/components/arcade_background/cubit/arcade_background_cubit.dart new file mode 100644 index 00000000..5b2188bc --- /dev/null +++ b/packages/pinball_components/lib/src/components/arcade_background/cubit/arcade_background_cubit.dart @@ -0,0 +1,15 @@ +// ignore_for_file: public_member_api_docs + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +part 'arcade_background_state.dart'; + +class ArcadeBackgroundCubit extends Cubit { + ArcadeBackgroundCubit() : super(const ArcadeBackgroundState.initial()); + + void onCharacterSelected(CharacterTheme characterTheme) { + emit(ArcadeBackgroundState(characterTheme: characterTheme)); + } +} diff --git a/packages/pinball_components/lib/src/components/arcade_background/cubit/arcade_background_state.dart b/packages/pinball_components/lib/src/components/arcade_background/cubit/arcade_background_state.dart new file mode 100644 index 00000000..d2406105 --- /dev/null +++ b/packages/pinball_components/lib/src/components/arcade_background/cubit/arcade_background_state.dart @@ -0,0 +1,15 @@ +// ignore_for_file: public_member_api_docs + +part of 'arcade_background_cubit.dart'; + +class ArcadeBackgroundState extends Equatable { + const ArcadeBackgroundState({required this.characterTheme}); + + const ArcadeBackgroundState.initial() + : this(characterTheme: const DashTheme()); + + final CharacterTheme characterTheme; + + @override + List get props => [characterTheme]; +} diff --git a/packages/pinball_components/lib/src/components/arrow_icon.dart b/packages/pinball_components/lib/src/components/arrow_icon.dart new file mode 100644 index 00000000..0dc33b10 --- /dev/null +++ b/packages/pinball_components/lib/src/components/arrow_icon.dart @@ -0,0 +1,49 @@ +import 'package:flame/components.dart'; +import 'package:flame/input.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball_components/pinball_components.dart'; + +/// enum with the available directions for an [ArrowIcon]. +enum ArrowIconDirection { + /// Left. + left, + + /// Right. + right, +} + +/// {@template arrow_icon} +/// A [SpriteComponent] that renders a simple arrow icon. +/// {@endtemplate} +class ArrowIcon extends SpriteComponent with Tappable, HasGameRef { + /// {@macro arrow_icon} + ArrowIcon({ + required Vector2 position, + required this.direction, + required this.onTap, + }) : super(position: position); + + final ArrowIconDirection direction; + final VoidCallback onTap; + + @override + Future onLoad() async { + anchor = Anchor.center; + final sprite = Sprite( + gameRef.images.fromCache( + direction == ArrowIconDirection.left + ? Assets.images.displayArrows.arrowLeft.keyName + : Assets.images.displayArrows.arrowRight.keyName, + ), + ); + + size = sprite.originalSize / 20; + this.sprite = sprite; + } + + @override + bool onTapUp(TapUpInfo info) { + onTap(); + return true; + } +} diff --git a/packages/pinball_components/lib/src/components/ball/cubit/ball_cubit.dart b/packages/pinball_components/lib/src/components/ball/cubit/ball_cubit.dart index 7d5c7dda..248a0e4f 100644 --- a/packages/pinball_components/lib/src/components/ball/cubit/ball_cubit.dart +++ b/packages/pinball_components/lib/src/components/ball/cubit/ball_cubit.dart @@ -7,7 +7,7 @@ part 'ball_state.dart'; class BallCubit extends Cubit { BallCubit() : super(const BallState.initial()); - void onThemeChanged(CharacterTheme characterTheme) { + void onCharacterSelected(CharacterTheme characterTheme) { emit(BallState(characterTheme: characterTheme)); } } diff --git a/packages/pinball_components/lib/src/components/baseboard.dart b/packages/pinball_components/lib/src/components/baseboard.dart index 47ba4666..2965d17f 100644 --- a/packages/pinball_components/lib/src/components/baseboard.dart +++ b/packages/pinball_components/lib/src/components/baseboard.dart @@ -3,6 +3,7 @@ import 'dart:math' as math; import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; /// {@template baseboard} /// Wing-shaped board piece to corral the [Ball] towards the [Flipper]s. diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index 5596c126..8fd74268 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -1,6 +1,8 @@ export 'android_animatronic.dart'; export 'android_bumper/android_bumper.dart'; export 'android_spaceship/android_spaceship.dart'; +export 'arcade_background/arcade_background.dart'; +export 'arrow_icon.dart'; export 'ball/ball.dart'; export 'baseboard.dart'; export 'board_background_sprite_component.dart'; @@ -15,7 +17,9 @@ export 'dino_walls.dart'; export 'error_component.dart'; export 'flapper/flapper.dart'; export 'flipper/flipper.dart'; -export 'google_letter/google_letter.dart'; +export 'google_letter.dart'; +export 'google_rollover/google_rollover.dart'; +export 'google_word/google_word.dart'; export 'initial_position.dart'; export 'joint_anchor.dart'; export 'kicker/kicker.dart'; @@ -26,7 +30,6 @@ export 'multiplier/multiplier.dart'; export 'plunger/plunger.dart'; export 'rocket.dart'; export 'score_component/score_component.dart'; -export 'shapes/shapes.dart'; export 'signpost/signpost.dart'; export 'skill_shot/skill_shot.dart'; export 'slingshot.dart'; diff --git a/packages/pinball_components/lib/src/components/google_letter.dart b/packages/pinball_components/lib/src/components/google_letter.dart new file mode 100644 index 00000000..1c63f7ff --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_letter.dart @@ -0,0 +1,88 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +enum GoogleLetterSpriteState { + lit, + dimmed, +} + +/// {@template google_letter} +/// Circular decal that represents a letter in "GOOGLE" for a given index. +/// {@endtemplate} +class GoogleLetter extends SpriteGroupComponent + with HasGameRef, FlameBlocListenable { + /// {@macro google_letter} + GoogleLetter(int index) + : _litAssetPath = _spritePaths[index][GoogleLetterSpriteState.lit]!, + _dimmedAssetPath = _spritePaths[index][GoogleLetterSpriteState.dimmed]!, + _index = index, + super(anchor: Anchor.center); + + final String _litAssetPath; + final String _dimmedAssetPath; + final int _index; + + @override + bool listenWhen(GoogleWordState previousState, GoogleWordState newState) { + return previousState.letterSpriteStates[_index] != + newState.letterSpriteStates[_index]; + } + + @override + void onNewState(GoogleWordState state) => + current = state.letterSpriteStates[_index]; + + @override + Future onLoad() async { + await super.onLoad(); + + final sprites = { + GoogleLetterSpriteState.lit: Sprite( + gameRef.images.fromCache(_litAssetPath), + ), + GoogleLetterSpriteState.dimmed: Sprite( + gameRef.images.fromCache(_dimmedAssetPath), + ), + }; + this.sprites = sprites; + current = readBloc() + .state + .letterSpriteStates[_index]; + size = sprites[current]!.originalSize / 10; + } +} + +final _spritePaths = >[ + { + GoogleLetterSpriteState.lit: Assets.images.googleWord.letter1.lit.keyName, + GoogleLetterSpriteState.dimmed: + Assets.images.googleWord.letter1.dimmed.keyName, + }, + { + GoogleLetterSpriteState.lit: Assets.images.googleWord.letter2.lit.keyName, + GoogleLetterSpriteState.dimmed: + Assets.images.googleWord.letter2.dimmed.keyName, + }, + { + GoogleLetterSpriteState.lit: Assets.images.googleWord.letter3.lit.keyName, + GoogleLetterSpriteState.dimmed: + Assets.images.googleWord.letter3.dimmed.keyName, + }, + { + GoogleLetterSpriteState.lit: Assets.images.googleWord.letter4.lit.keyName, + GoogleLetterSpriteState.dimmed: + Assets.images.googleWord.letter4.dimmed.keyName, + }, + { + GoogleLetterSpriteState.lit: Assets.images.googleWord.letter5.lit.keyName, + GoogleLetterSpriteState.dimmed: + Assets.images.googleWord.letter5.dimmed.keyName, + }, + { + GoogleLetterSpriteState.lit: Assets.images.googleWord.letter6.lit.keyName, + GoogleLetterSpriteState.dimmed: + Assets.images.googleWord.letter6.dimmed.keyName, + }, +]; diff --git a/packages/pinball_components/lib/src/components/google_letter/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/google_letter/behaviors/behaviors.dart deleted file mode 100644 index df54c1f4..00000000 --- a/packages/pinball_components/lib/src/components/google_letter/behaviors/behaviors.dart +++ /dev/null @@ -1 +0,0 @@ -export 'google_letter_ball_contact_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart b/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart deleted file mode 100644 index 99b15702..00000000 --- a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:bloc/bloc.dart'; - -part 'google_letter_state.dart'; - -class GoogleLetterCubit extends Cubit { - GoogleLetterCubit() : super(GoogleLetterState.dimmed); - - void onBallContacted() { - emit(GoogleLetterState.lit); - } - - void onReset() { - emit(GoogleLetterState.dimmed); - } -} diff --git a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart b/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart deleted file mode 100644 index 12c7edd0..00000000 --- a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart +++ /dev/null @@ -1,6 +0,0 @@ -part of 'google_letter_cubit.dart'; - -enum GoogleLetterState { - lit, - dimmed, -} diff --git a/packages/pinball_components/lib/src/components/google_letter/google_letter.dart b/packages/pinball_components/lib/src/components/google_letter/google_letter.dart deleted file mode 100644 index 9d678e30..00000000 --- a/packages/pinball_components/lib/src/components/google_letter/google_letter.dart +++ /dev/null @@ -1,133 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flutter/material.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_components/src/components/google_letter/behaviors/behaviors.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -export 'cubit/google_letter_cubit.dart'; - -final _spritePaths = >[ - { - GoogleLetterState.lit: Assets.images.googleWord.letter1.lit.keyName, - GoogleLetterState.dimmed: Assets.images.googleWord.letter1.dimmed.keyName, - }, - { - GoogleLetterState.lit: Assets.images.googleWord.letter2.lit.keyName, - GoogleLetterState.dimmed: Assets.images.googleWord.letter2.dimmed.keyName, - }, - { - GoogleLetterState.lit: Assets.images.googleWord.letter3.lit.keyName, - GoogleLetterState.dimmed: Assets.images.googleWord.letter3.dimmed.keyName, - }, - { - GoogleLetterState.lit: Assets.images.googleWord.letter4.lit.keyName, - GoogleLetterState.dimmed: Assets.images.googleWord.letter4.dimmed.keyName, - }, - { - GoogleLetterState.lit: Assets.images.googleWord.letter5.lit.keyName, - GoogleLetterState.dimmed: Assets.images.googleWord.letter5.dimmed.keyName, - }, - { - GoogleLetterState.lit: Assets.images.googleWord.letter6.lit.keyName, - GoogleLetterState.dimmed: Assets.images.googleWord.letter6.dimmed.keyName, - }, -]; - -/// {@template google_letter} -/// Circular sensor that represents a letter in "GOOGLE" for a given index. -/// {@endtemplate} -class GoogleLetter extends BodyComponent with InitialPosition { - /// {@macro google_letter} - GoogleLetter( - int index, { - Iterable? children, - }) : this._( - index, - bloc: GoogleLetterCubit(), - children: children, - ); - - GoogleLetter._( - int index, { - required this.bloc, - Iterable? children, - }) : super( - children: [ - _GoogleLetterSpriteGroupComponent( - litAssetPath: _spritePaths[index][GoogleLetterState.lit]!, - dimmedAssetPath: _spritePaths[index][GoogleLetterState.dimmed]!, - current: bloc.state, - ), - GoogleLetterBallContactBehavior(), - ...?children, - ], - renderBody: false, - ); - - /// Creates a [GoogleLetter] without any children. - /// - /// This can be used for testing [GoogleLetter]'s behaviors in isolation. - @visibleForTesting - GoogleLetter.test({ - required this.bloc, - }); - - final GoogleLetterCubit bloc; - - @override - void onRemove() { - bloc.close(); - super.onRemove(); - } - - @override - Body createBody() { - final shape = CircleShape()..radius = 1.85; - final fixtureDef = FixtureDef( - shape, - isSensor: true, - ); - final bodyDef = BodyDef( - position: initialPosition, - userData: this, - ); - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } -} - -class _GoogleLetterSpriteGroupComponent - extends SpriteGroupComponent - with HasGameRef, ParentIsA { - _GoogleLetterSpriteGroupComponent({ - required String litAssetPath, - required String dimmedAssetPath, - required GoogleLetterState current, - }) : _litAssetPath = litAssetPath, - _dimmedAssetPath = dimmedAssetPath, - super( - anchor: Anchor.center, - current: current, - ); - - final String _litAssetPath; - final String _dimmedAssetPath; - - @override - Future onLoad() async { - await super.onLoad(); - parent.bloc.stream.listen((state) => current = state); - - final sprites = { - GoogleLetterState.lit: Sprite( - gameRef.images.fromCache(_litAssetPath), - ), - GoogleLetterState.dimmed: Sprite( - gameRef.images.fromCache(_dimmedAssetPath), - ), - }; - this.sprites = sprites; - size = sprites[current]!.originalSize / 10; - } -} diff --git a/packages/pinball_components/lib/src/components/google_rollover/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/google_rollover/behaviors/behaviors.dart new file mode 100644 index 00000000..0cb1ea1a --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_rollover/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'google_rollover_ball_contact_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/google_letter/behaviors/google_letter_ball_contact_behavior.dart b/packages/pinball_components/lib/src/components/google_rollover/behaviors/google_rollover_ball_contact_behavior.dart similarity index 53% rename from packages/pinball_components/lib/src/components/google_letter/behaviors/google_letter_ball_contact_behavior.dart rename to packages/pinball_components/lib/src/components/google_rollover/behaviors/google_rollover_ball_contact_behavior.dart index 84a210ef..bef08bb0 100644 --- a/packages/pinball_components/lib/src/components/google_letter/behaviors/google_letter_ball_contact_behavior.dart +++ b/packages/pinball_components/lib/src/components/google_rollover/behaviors/google_rollover_ball_contact_behavior.dart @@ -1,12 +1,15 @@ +import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -class GoogleLetterBallContactBehavior extends ContactBehavior { +class GoogleRolloverBallContactBehavior + extends ContactBehavior { @override void beginContact(Object other, Contact contact) { super.beginContact(other, contact); if (other is! Ball) return; - parent.bloc.onBallContacted(); + readBloc().onRolloverContacted(); + parent.firstChild()?.playing = true; } } diff --git a/packages/pinball_components/lib/src/components/google_rollover/google_rollover.dart b/packages/pinball_components/lib/src/components/google_rollover/google_rollover.dart new file mode 100644 index 00000000..e04e9778 --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_rollover/google_rollover.dart @@ -0,0 +1,113 @@ +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/google_rollover/behaviors/behaviors.dart'; + +/// {@template google_rollover} +/// Rollover that lights up [GoogleLetter]s. +/// {@endtemplate} +class GoogleRollover extends BodyComponent { + /// {@macro google_rollover} + GoogleRollover({ + required BoardSide side, + Iterable? children, + }) : _side = side, + super( + renderBody: false, + children: [ + GoogleRolloverBallContactBehavior(), + _RolloverDecalSpriteComponent(side: side), + _PinSpriteAnimationComponent(side: side), + ...?children, + ], + ); + + final BoardSide _side; + + @override + Body createBody() { + final shape = PolygonShape() + ..setAsBox( + 0.1, + 3.4, + Vector2(_side.isLeft ? -14.8 : 5.9, -11), + 0.19 * _side.direction, + ); + final fixtureDef = FixtureDef(shape, isSensor: true); + return world.createBody(BodyDef())..createFixture(fixtureDef); + } +} + +class _RolloverDecalSpriteComponent extends SpriteComponent with HasGameRef { + _RolloverDecalSpriteComponent({required BoardSide side}) + : _side = side, + super( + anchor: Anchor.center, + position: Vector2(side.isLeft ? -14.8 : 5.9, -11), + angle: 0.18 * side.direction, + ); + + final BoardSide _side; + + @override + Future onLoad() async { + await super.onLoad(); + + final sprite = Sprite( + gameRef.images.fromCache( + (_side.isLeft) + ? Assets.images.googleRollover.left.decal.keyName + : Assets.images.googleRollover.right.decal.keyName, + ), + ); + this.sprite = sprite; + size = sprite.originalSize / 20; + } +} + +class _PinSpriteAnimationComponent extends SpriteAnimationComponent + with HasGameRef { + _PinSpriteAnimationComponent({required BoardSide side}) + : _side = side, + super( + anchor: Anchor.center, + position: Vector2(side.isLeft ? -14.9 : 5.95, -11), + angle: 0, + playing: false, + ); + + final BoardSide _side; + + @override + Future onLoad() async { + await super.onLoad(); + + final spriteSheet = gameRef.images.fromCache( + _side.isLeft + ? Assets.images.googleRollover.left.pin.keyName + : Assets.images.googleRollover.right.pin.keyName, + ); + + const amountPerRow = 3; + const amountPerColumn = 1; + final textureSize = Vector2( + spriteSheet.width / amountPerRow, + spriteSheet.height / amountPerColumn, + ); + size = textureSize / 10; + + animation = SpriteAnimation.fromFrameData( + spriteSheet, + SpriteAnimationData.sequenced( + amount: amountPerRow * amountPerColumn, + amountPerRow: amountPerRow, + stepTime: 1 / 24, + textureSize: textureSize, + loop: false, + ), + )..onComplete = () { + animation?.reset(); + playing = false; + }; + } +} diff --git a/packages/pinball_components/lib/src/components/google_word/cubit/google_word_cubit.dart b/packages/pinball_components/lib/src/components/google_word/cubit/google_word_cubit.dart new file mode 100644 index 00000000..197771d6 --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_word/cubit/google_word_cubit.dart @@ -0,0 +1,30 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:pinball_components/pinball_components.dart'; + +part 'google_word_state.dart'; + +class GoogleWordCubit extends Cubit { + GoogleWordCubit() : super(GoogleWordState.initial()); + + static const _lettersInGoogle = 6; + + int _lastLitLetter = 0; + + void onRolloverContacted() { + final spriteStatesMap = {...state.letterSpriteStates}; + if (_lastLitLetter < _lettersInGoogle) { + spriteStatesMap.update( + _lastLitLetter, + (_) => GoogleLetterSpriteState.lit, + ); + emit(GoogleWordState(letterSpriteStates: spriteStatesMap)); + _lastLitLetter++; + } + } + + void onBonusAwarded() { + emit(GoogleWordState.initial()); + _lastLitLetter = 0; + } +} diff --git a/packages/pinball_components/lib/src/components/google_word/cubit/google_word_state.dart b/packages/pinball_components/lib/src/components/google_word/cubit/google_word_state.dart new file mode 100644 index 00000000..a1ee2786 --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_word/cubit/google_word_state.dart @@ -0,0 +1,17 @@ +part of 'google_word_cubit.dart'; + +class GoogleWordState extends Equatable { + const GoogleWordState({required this.letterSpriteStates}); + + GoogleWordState.initial() + : this( + letterSpriteStates: { + for (var i = 0; i <= 5; i++) i: GoogleLetterSpriteState.dimmed + }, + ); + + final Map letterSpriteStates; + + @override + List get props => [...letterSpriteStates.values]; +} diff --git a/packages/pinball_components/lib/src/components/google_word/google_word.dart b/packages/pinball_components/lib/src/components/google_word/google_word.dart new file mode 100644 index 00000000..72126d2c --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_word/google_word.dart @@ -0,0 +1,24 @@ +import 'package:flame/components.dart'; +import 'package:pinball_components/pinball_components.dart'; + +export 'cubit/google_word_cubit.dart'; + +/// {@template google_word} +/// Loads all [GoogleLetter]s to compose a [GoogleWord]. +/// {@endtemplate} +class GoogleWord extends PositionComponent { + /// {@macro google_word} + GoogleWord({ + required Vector2 position, + }) : super( + position: position, + children: [ + GoogleLetter(0)..position = Vector2(-13.1, 1.72), + GoogleLetter(1)..position = Vector2(-8.33, -0.75), + GoogleLetter(2)..position = Vector2(-2.88, -1.85), + GoogleLetter(3)..position = Vector2(2.88, -1.85), + GoogleLetter(4)..position = Vector2(8.33, -0.75), + GoogleLetter(5)..position = Vector2(13.1, 1.72), + ], + ); +} diff --git a/packages/pinball_components/lib/src/components/multiball/behaviors/multiball_blinking_behavior.dart b/packages/pinball_components/lib/src/components/multiball/behaviors/multiball_blinking_behavior.dart index 48c90552..b974f33c 100644 --- a/packages/pinball_components/lib/src/components/multiball/behaviors/multiball_blinking_behavior.dart +++ b/packages/pinball_components/lib/src/components/multiball/behaviors/multiball_blinking_behavior.dart @@ -9,9 +9,9 @@ import 'package:pinball_flame/pinball_flame.dart'; class MultiballBlinkingBehavior extends TimerComponent with ParentIsA { /// {@macro multiball_blinking_behavior} - MultiballBlinkingBehavior() : super(period: 0.1); + MultiballBlinkingBehavior() : super(period: 0.18); - final _maxBlinks = 10; + final _maxBlinks = 28; int _blinksCounter = 0; diff --git a/packages/pinball_components/lib/src/components/shapes/arc_shape.dart b/packages/pinball_components/lib/src/components/shapes/arc_shape.dart deleted file mode 100644 index d58bdf1e..00000000 --- a/packages/pinball_components/lib/src/components/shapes/arc_shape.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:geometry/geometry.dart'; - -/// {@template arc_shape} -/// Creates an arc. -/// {@endtemplate} -class ArcShape extends ChainShape { - /// {@macro arc_shape} - ArcShape({ - required this.center, - required this.arcRadius, - required this.angle, - this.rotation = 0, - }) { - createChain( - calculateArc( - center: center, - radius: arcRadius, - angle: angle, - offsetAngle: rotation, - ), - ); - } - - /// The center of the arc. - final Vector2 center; - - /// The radius of the arc. - final double arcRadius; - - /// Specifies the size of the arc, in radians. - /// - /// For example, two pi returns a complete circumference. - final double angle; - - /// Angle in radians to rotate the arc around its [center]. - final double rotation; - - ArcShape copyWith({ - Vector2? center, - double? arcRadius, - double? angle, - double? rotation, - }) => - ArcShape( - center: center ?? this.center, - arcRadius: arcRadius ?? this.arcRadius, - angle: angle ?? this.angle, - rotation: rotation ?? this.rotation, - ); -} diff --git a/packages/pinball_components/lib/src/components/z_indexes.dart b/packages/pinball_components/lib/src/components/z_indexes.dart index 0d0fab98..543ad7a7 100644 --- a/packages/pinball_components/lib/src/components/z_indexes.dart +++ b/packages/pinball_components/lib/src/components/z_indexes.dart @@ -18,6 +18,8 @@ abstract class ZIndexes { // Background + static const arcadeBackground = _below + boardBackground; + static const boardBackground = 5 * _below + _base; static const decal = _above + boardBackground; diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index 0f59d47e..7065911b 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -17,8 +17,6 @@ dependencies: ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f flutter: sdk: flutter - geometry: - path: ../geometry intl: ^0.17.0 pinball_audio: path: ../pinball_audio @@ -86,6 +84,8 @@ flutter: - assets/images/google_word/letter4/ - assets/images/google_word/letter5/ - assets/images/google_word/letter6/ + - assets/images/google_rollover/left/ + - assets/images/google_rollover/right/ - assets/images/signpost/ - assets/images/multiball/ - assets/images/multiplier/x2/ @@ -95,8 +95,10 @@ flutter: - assets/images/multiplier/x6/ - assets/images/score/ - assets/images/backbox/ + - assets/images/backbox/button/ - assets/images/flapper/ - assets/images/skill_shot/ + - assets/images/display_arrows/ flutter_gen: line_length: 80 diff --git a/packages/pinball_components/sandbox/lib/common/add_game.dart b/packages/pinball_components/sandbox/lib/common/add_game.dart index 5b6388d3..04e08c01 100644 --- a/packages/pinball_components/sandbox/lib/common/add_game.dart +++ b/packages/pinball_components/sandbox/lib/common/add_game.dart @@ -3,7 +3,7 @@ import 'package:flame/game.dart'; import 'package:sandbox/common/common.dart'; const _path = - 'https://github.com/VGVentures/pinball/tree/main/packages/pinball_components/sandbox/lib/stories/'; + 'https://github.com/flutter/pinball/tree/main/packages/pinball_components/sandbox/lib/stories/'; extension StoryAddGame on Story { void addGame({ diff --git a/packages/pinball_components/sandbox/lib/main.dart b/packages/pinball_components/sandbox/lib/main.dart index 714bbee5..fb948a89 100644 --- a/packages/pinball_components/sandbox/lib/main.dart +++ b/packages/pinball_components/sandbox/lib/main.dart @@ -21,6 +21,7 @@ void main() { addScoreStories(dashbook); addMultiballStories(dashbook); addMultipliersStories(dashbook); + addArrowIconStories(dashbook); runApp(dashbook); } diff --git a/packages/pinball_components/sandbox/lib/stories/arrow_icon/arrow_icon_game.dart b/packages/pinball_components/sandbox/lib/stories/arrow_icon/arrow_icon_game.dart new file mode 100644 index 00000000..23af63b0 --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/arrow_icon/arrow_icon_game.dart @@ -0,0 +1,37 @@ +import 'package:flame/game.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:sandbox/common/games.dart'; + +class ArrowIconGame extends AssetsGame with HasTappables { + ArrowIconGame() + : super( + imagesFileNames: [ + Assets.images.displayArrows.arrowLeft.keyName, + Assets.images.displayArrows.arrowRight.keyName, + ], + ); + + static const description = 'Shows how ArrowIcons are rendered.'; + + @override + Future onLoad() async { + await super.onLoad(); + camera.followVector2(Vector2.zero()); + + await add( + ArrowIcon( + position: Vector2.zero(), + direction: ArrowIconDirection.left, + onTap: () {}, + ), + ); + + await add( + ArrowIcon( + position: Vector2(0, 20), + direction: ArrowIconDirection.right, + onTap: () {}, + ), + ); + } +} diff --git a/packages/pinball_components/sandbox/lib/stories/arrow_icon/stories.dart b/packages/pinball_components/sandbox/lib/stories/arrow_icon/stories.dart new file mode 100644 index 00000000..05a8a8ff --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/arrow_icon/stories.dart @@ -0,0 +1,11 @@ +import 'package:dashbook/dashbook.dart'; +import 'package:sandbox/common/common.dart'; +import 'package:sandbox/stories/arrow_icon/arrow_icon_game.dart'; + +void addArrowIconStories(Dashbook dashbook) { + dashbook.storiesOf('ArrowIcon').addGame( + title: 'Basic', + description: ArrowIconGame.description, + gameBuilder: (context) => ArrowIconGame(), + ); +} diff --git a/packages/pinball_components/sandbox/lib/stories/stories.dart b/packages/pinball_components/sandbox/lib/stories/stories.dart index 0a514eb9..8a165693 100644 --- a/packages/pinball_components/sandbox/lib/stories/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/stories.dart @@ -1,4 +1,5 @@ export 'android_acres/stories.dart'; +export 'arrow_icon/stories.dart'; export 'ball/stories.dart'; export 'bottom_group/stories.dart'; export 'boundaries/stories.dart'; diff --git a/packages/pinball_components/test/helpers/test_game.dart b/packages/pinball_components/test/helpers/test_game.dart index 1f8b9ee6..57c7961c 100644 --- a/packages/pinball_components/test/helpers/test_game.dart +++ b/packages/pinball_components/test/helpers/test_game.dart @@ -1,3 +1,4 @@ +import 'package:flame/game.dart'; import 'package:flame/input.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; @@ -20,3 +21,7 @@ class TestGame extends Forge2DGame { class KeyboardTestGame extends TestGame with HasKeyboardHandlerComponents { KeyboardTestGame([List? assets]) : super(assets); } + +class TappablesTestGame extends TestGame with HasTappables { + TappablesTestGame([List? assets]) : super(assets); +} diff --git a/packages/pinball_components/test/src/components/arcade_background/arcade_background_test.dart b/packages/pinball_components/test/src/components/arcade_background/arcade_background_test.dart new file mode 100644 index 00000000..a5c336d5 --- /dev/null +++ b/packages/pinball_components/test/src/components/arcade_background/arcade_background_test.dart @@ -0,0 +1,79 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_theme/pinball_theme.dart' as theme; + +import '../../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + theme.Assets.images.android.background.keyName, + theme.Assets.images.dash.background.keyName, + theme.Assets.images.dino.background.keyName, + theme.Assets.images.sparky.background.keyName, + ]; + + final flameTester = FlameTester(() => TestGame(assets)); + + group('ArcadeBackground', () { + test( + 'can be instantiated', + () { + expect(ArcadeBackground(), isA()); + expect(ArcadeBackground.test(), isA()); + }, + ); + + flameTester.test( + 'loads correctly', + (game) async { + final ball = ArcadeBackground(); + await game.ready(); + await game.ensureAdd(ball); + + expect(game.contains(ball), isTrue); + }, + ); + + flameTester.test( + 'has only one SpriteComponent', + (game) async { + final ball = ArcadeBackground(); + await game.ready(); + await game.ensureAdd(ball); + + expect( + ball.descendants().whereType().length, + equals(1), + ); + }, + ); + + flameTester.test( + 'ArcadeBackgroundSpriteComponent changes sprite onNewState', + (game) async { + final ball = ArcadeBackground(); + await game.ready(); + await game.ensureAdd(ball); + + final ballSprite = ball + .descendants() + .whereType() + .single; + final originalSprite = ballSprite.sprite; + + ballSprite.onNewState( + const ArcadeBackgroundState(characterTheme: theme.DinoTheme()), + ); + await game.ready(); + + final newSprite = ballSprite.sprite; + expect(newSprite != originalSprite, isTrue); + }, + ); + }); +} diff --git a/packages/pinball_components/test/src/components/arcade_background/cubit/arcade_background_cubit_test.dart b/packages/pinball_components/test/src/components/arcade_background/cubit/arcade_background_cubit_test.dart new file mode 100644 index 00000000..f2e99247 --- /dev/null +++ b/packages/pinball_components/test/src/components/arcade_background/cubit/arcade_background_cubit_test.dart @@ -0,0 +1,22 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +void main() { + group( + 'ArcadeBackgroundCubit', + () { + blocTest( + 'onCharacterSelected emits new theme', + build: ArcadeBackgroundCubit.new, + act: (bloc) => bloc.onCharacterSelected(const DinoTheme()), + expect: () => [ + const ArcadeBackgroundState( + characterTheme: DinoTheme(), + ), + ], + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/arcade_background/cubit/arcade_background_state_test.dart b/packages/pinball_components/test/src/components/arcade_background/cubit/arcade_background_state_test.dart new file mode 100644 index 00000000..97925fb6 --- /dev/null +++ b/packages/pinball_components/test/src/components/arcade_background/cubit/arcade_background_state_test.dart @@ -0,0 +1,32 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +void main() { + group('ArcadeBackgroundState', () { + test('supports value equality', () { + expect( + ArcadeBackgroundState(characterTheme: DashTheme()), + equals(ArcadeBackgroundState(characterTheme: DashTheme())), + ); + }); + + group('constructor', () { + test('can be instantiated', () { + expect( + ArcadeBackgroundState(characterTheme: DashTheme()), + isNotNull, + ); + }); + + test('initial contains DashTheme', () { + expect( + ArcadeBackgroundState.initial().characterTheme, + DashTheme(), + ); + }); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/arrow_icon_test.dart b/packages/pinball_components/test/src/components/arrow_icon_test.dart new file mode 100644 index 00000000..c8a1c5aa --- /dev/null +++ b/packages/pinball_components/test/src/components/arrow_icon_test.dart @@ -0,0 +1,96 @@ +// ignore_for_file: cascade_invocations, one_member_abstracts + +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +abstract class _VoidCallbackStubBase { + void onCall(); +} + +class _VoidCallbackStub extends Mock implements _VoidCallbackStubBase {} + +void main() { + group('ArrowIcon', () { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + Assets.images.displayArrows.arrowLeft.keyName, + Assets.images.displayArrows.arrowRight.keyName, + ]; + final flameTester = FlameTester(() => TappablesTestGame(assets)); + + flameTester.testGameWidget( + 'is tappable', + setUp: (game, tester) async { + final stub = _VoidCallbackStub(); + await game.images.loadAll(assets); + await game.ensureAdd( + ArrowIcon( + position: Vector2.zero(), + direction: ArrowIconDirection.left, + onTap: stub.onCall, + ), + ); + await tester.pump(); + await tester.tapAt(Offset.zero); + await tester.pump(); + }, + verify: (game, tester) async { + final icon = game.descendants().whereType().single; + verify(icon.onTap).called(1); + }, + ); + + group('left', () { + flameTester.testGameWidget( + 'renders correctly', + setUp: (game, tester) async { + await game.images.loadAll(assets); + game.camera.followVector2(Vector2.zero()); + await game.add( + ArrowIcon( + position: Vector2.zero(), + direction: ArrowIconDirection.left, + onTap: () {}, + ), + ); + await tester.pump(); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/arrow_icon_left.png'), + ); + }, + ); + }); + + group('right', () { + flameTester.testGameWidget( + 'renders correctly', + setUp: (game, tester) async { + await game.images.loadAll(assets); + game.camera.followVector2(Vector2.zero()); + await game.add( + ArrowIcon( + position: Vector2.zero(), + direction: ArrowIconDirection.right, + onTap: () {}, + ), + ); + await tester.pump(); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/arrow_icon_right.png'), + ); + }, + ); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/ball/cubit/ball_cubit_test.dart b/packages/pinball_components/test/src/components/ball/cubit/ball_cubit_test.dart index c5a03213..c3a89120 100644 --- a/packages/pinball_components/test/src/components/ball/cubit/ball_cubit_test.dart +++ b/packages/pinball_components/test/src/components/ball/cubit/ball_cubit_test.dart @@ -8,9 +8,9 @@ void main() { 'BallCubit', () { blocTest( - 'onThemeChanged emits new theme', + 'onCharacterSelected emits new theme', build: BallCubit.new, - act: (bloc) => bloc.onThemeChanged(const DinoTheme()), + act: (bloc) => bloc.onCharacterSelected(const DinoTheme()), expect: () => [const BallState(characterTheme: DinoTheme())], ); }, diff --git a/packages/pinball_components/test/src/components/board_background_sprite_component_test.dart b/packages/pinball_components/test/src/components/board_background_sprite_component_test.dart index df35594f..79e8c56b 100644 --- a/packages/pinball_components/test/src/components/board_background_sprite_component_test.dart +++ b/packages/pinball_components/test/src/components/board_background_sprite_component_test.dart @@ -40,7 +40,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('golden/board-background.png'), + matchesGoldenFile('golden/board_background.png'), ); }, ); diff --git a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/down.png b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/down.png index a84d84c2..58180a63 100644 Binary files a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/down.png and b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/down.png differ diff --git a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/middle.png b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/middle.png index 0515f5f5..b6d31b1f 100644 Binary files a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/middle.png and b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/middle.png differ diff --git a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/up.png b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/up.png index 0a2d4674..48a834c0 100644 Binary files a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/up.png and b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/up.png differ diff --git a/packages/pinball_components/test/src/components/dino_walls_test.dart b/packages/pinball_components/test/src/components/dino_walls_test.dart index 5e4471e5..dd8172ac 100644 --- a/packages/pinball_components/test/src/components/dino_walls_test.dart +++ b/packages/pinball_components/test/src/components/dino_walls_test.dart @@ -37,7 +37,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('golden/dino-walls.png'), + matchesGoldenFile('golden/dino_walls.png'), ); }, ); diff --git a/packages/pinball_components/test/src/components/golden/arrow_icon_left.png b/packages/pinball_components/test/src/components/golden/arrow_icon_left.png new file mode 100644 index 00000000..dab87d41 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/arrow_icon_left.png differ diff --git a/packages/pinball_components/test/src/components/golden/arrow_icon_right.png b/packages/pinball_components/test/src/components/golden/arrow_icon_right.png new file mode 100644 index 00000000..185e9a9a Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/arrow_icon_right.png differ diff --git a/packages/pinball_components/test/src/components/golden/baseboard.png b/packages/pinball_components/test/src/components/golden/baseboard.png index 01141551..4b051dfa 100644 Binary files a/packages/pinball_components/test/src/components/golden/baseboard.png and b/packages/pinball_components/test/src/components/golden/baseboard.png differ diff --git a/packages/pinball_components/test/src/components/golden/board-background.png b/packages/pinball_components/test/src/components/golden/board-background.png deleted file mode 100644 index 31abceb1..00000000 Binary files a/packages/pinball_components/test/src/components/golden/board-background.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/board_background.png b/packages/pinball_components/test/src/components/golden/board_background.png new file mode 100644 index 00000000..c15a29ce Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/board_background.png differ diff --git a/packages/pinball_components/test/src/components/golden/boundaries.png b/packages/pinball_components/test/src/components/golden/boundaries.png index e8075f63..ab6716dd 100644 Binary files a/packages/pinball_components/test/src/components/golden/boundaries.png and b/packages/pinball_components/test/src/components/golden/boundaries.png differ diff --git a/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png b/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png index 035a152f..f31d05e0 100644 Binary files a/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png and b/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png differ diff --git a/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png b/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png index 23c1142d..f2074da8 100644 Binary files a/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png and b/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png differ diff --git a/packages/pinball_components/test/src/components/golden/camera_zoom/no_zoom.png b/packages/pinball_components/test/src/components/golden/camera_zoom/no_zoom.png index 200ab49f..2975ac0d 100644 Binary files a/packages/pinball_components/test/src/components/golden/camera_zoom/no_zoom.png and b/packages/pinball_components/test/src/components/golden/camera_zoom/no_zoom.png differ diff --git a/packages/pinball_components/test/src/components/golden/dash_animatronic/end.png b/packages/pinball_components/test/src/components/golden/dash_animatronic/end.png index c8218fe1..3ee917ee 100644 Binary files a/packages/pinball_components/test/src/components/golden/dash_animatronic/end.png and b/packages/pinball_components/test/src/components/golden/dash_animatronic/end.png differ diff --git a/packages/pinball_components/test/src/components/golden/dash_animatronic/middle.png b/packages/pinball_components/test/src/components/golden/dash_animatronic/middle.png index 9e79695a..e57d0f07 100644 Binary files a/packages/pinball_components/test/src/components/golden/dash_animatronic/middle.png and b/packages/pinball_components/test/src/components/golden/dash_animatronic/middle.png differ diff --git a/packages/pinball_components/test/src/components/golden/dash_animatronic/start.png b/packages/pinball_components/test/src/components/golden/dash_animatronic/start.png index 3e5e91f5..5a16627d 100644 Binary files a/packages/pinball_components/test/src/components/golden/dash_animatronic/start.png and b/packages/pinball_components/test/src/components/golden/dash_animatronic/start.png differ diff --git a/packages/pinball_components/test/src/components/golden/dino-walls.png b/packages/pinball_components/test/src/components/golden/dino_walls.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/dino-walls.png rename to packages/pinball_components/test/src/components/golden/dino_walls.png diff --git a/packages/pinball_components/test/src/components/golden/launch-ramp.png b/packages/pinball_components/test/src/components/golden/launch-ramp.png deleted file mode 100644 index 50a4ed4c..00000000 Binary files a/packages/pinball_components/test/src/components/golden/launch-ramp.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/launch_ramp.png b/packages/pinball_components/test/src/components/golden/launch_ramp.png new file mode 100644 index 00000000..0ff6ef45 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/launch_ramp.png differ diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x2-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x2_dimmed.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x2-dimmed.png rename to packages/pinball_components/test/src/components/golden/multipliers/x2_dimmed.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x2-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x2_lit.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x2-lit.png rename to packages/pinball_components/test/src/components/golden/multipliers/x2_lit.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x3-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x3_dimmed.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x3-dimmed.png rename to packages/pinball_components/test/src/components/golden/multipliers/x3_dimmed.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x3-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x3_lit.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x3-lit.png rename to packages/pinball_components/test/src/components/golden/multipliers/x3_lit.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x4-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x4_dimmed.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x4-dimmed.png rename to packages/pinball_components/test/src/components/golden/multipliers/x4_dimmed.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x4-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x4_lit.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x4-lit.png rename to packages/pinball_components/test/src/components/golden/multipliers/x4_lit.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x5-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x5_dimmed.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x5-dimmed.png rename to packages/pinball_components/test/src/components/golden/multipliers/x5_dimmed.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x5-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x5_lit.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x5-lit.png rename to packages/pinball_components/test/src/components/golden/multipliers/x5_lit.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x6-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x6_dimmed.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x6-dimmed.png rename to packages/pinball_components/test/src/components/golden/multipliers/x6_dimmed.png diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x6-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x6_lit.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/multipliers/x6-lit.png rename to packages/pinball_components/test/src/components/golden/multipliers/x6_lit.png diff --git a/packages/pinball_components/test/src/components/golden/rocket.png b/packages/pinball_components/test/src/components/golden/rocket.png index f9dc36f8..8ee4009d 100644 Binary files a/packages/pinball_components/test/src/components/golden/rocket.png and b/packages/pinball_components/test/src/components/golden/rocket.png differ diff --git a/packages/pinball_components/test/src/components/golden/score/1m.png b/packages/pinball_components/test/src/components/golden/score/1m.png index bb2f5631..7045a039 100644 Binary files a/packages/pinball_components/test/src/components/golden/score/1m.png and b/packages/pinball_components/test/src/components/golden/score/1m.png differ diff --git a/packages/pinball_components/test/src/components/golden/score/200k.png b/packages/pinball_components/test/src/components/golden/score/200k.png index c25d116b..46503f3d 100644 Binary files a/packages/pinball_components/test/src/components/golden/score/200k.png and b/packages/pinball_components/test/src/components/golden/score/200k.png differ diff --git a/packages/pinball_components/test/src/components/golden/score/20k.png b/packages/pinball_components/test/src/components/golden/score/20k.png index 2a4446c3..42721119 100644 Binary files a/packages/pinball_components/test/src/components/golden/score/20k.png and b/packages/pinball_components/test/src/components/golden/score/20k.png differ diff --git a/packages/pinball_components/test/src/components/golden/score/5k.png b/packages/pinball_components/test/src/components/golden/score/5k.png index 8f2a7973..1e5117f1 100644 Binary files a/packages/pinball_components/test/src/components/golden/score/5k.png and b/packages/pinball_components/test/src/components/golden/score/5k.png differ diff --git a/packages/pinball_components/test/src/components/golden/signpost/active1.png b/packages/pinball_components/test/src/components/golden/signpost/active1.png index 0e0f9e79..9b8ac5dd 100644 Binary files a/packages/pinball_components/test/src/components/golden/signpost/active1.png and b/packages/pinball_components/test/src/components/golden/signpost/active1.png differ diff --git a/packages/pinball_components/test/src/components/golden/signpost/active2.png b/packages/pinball_components/test/src/components/golden/signpost/active2.png index 9dfae564..e86ee7bf 100644 Binary files a/packages/pinball_components/test/src/components/golden/signpost/active2.png and b/packages/pinball_components/test/src/components/golden/signpost/active2.png differ diff --git a/packages/pinball_components/test/src/components/golden/signpost/active3.png b/packages/pinball_components/test/src/components/golden/signpost/active3.png index a99c9e48..557e9605 100644 Binary files a/packages/pinball_components/test/src/components/golden/signpost/active3.png and b/packages/pinball_components/test/src/components/golden/signpost/active3.png differ diff --git a/packages/pinball_components/test/src/components/golden/signpost/inactive.png b/packages/pinball_components/test/src/components/golden/signpost/inactive.png index 7f089716..5e67d706 100644 Binary files a/packages/pinball_components/test/src/components/golden/signpost/inactive.png and b/packages/pinball_components/test/src/components/golden/signpost/inactive.png differ diff --git a/packages/pinball_components/test/src/components/golden/spaceship-rail.png b/packages/pinball_components/test/src/components/golden/spaceship_rail.png similarity index 100% rename from packages/pinball_components/test/src/components/golden/spaceship-rail.png rename to packages/pinball_components/test/src/components/golden/spaceship_rail.png diff --git a/packages/pinball_components/test/src/components/golden/sparky-computer.png b/packages/pinball_components/test/src/components/golden/sparky-computer.png deleted file mode 100644 index ebe2e98e..00000000 Binary files a/packages/pinball_components/test/src/components/golden/sparky-computer.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/sparky_animatronic/end.png b/packages/pinball_components/test/src/components/golden/sparky_animatronic/end.png index 5e963f14..d499e754 100644 Binary files a/packages/pinball_components/test/src/components/golden/sparky_animatronic/end.png and b/packages/pinball_components/test/src/components/golden/sparky_animatronic/end.png differ diff --git a/packages/pinball_components/test/src/components/golden/sparky_animatronic/middle.png b/packages/pinball_components/test/src/components/golden/sparky_animatronic/middle.png index 2665c5cb..dea793d9 100644 Binary files a/packages/pinball_components/test/src/components/golden/sparky_animatronic/middle.png and b/packages/pinball_components/test/src/components/golden/sparky_animatronic/middle.png differ diff --git a/packages/pinball_components/test/src/components/golden/sparky_animatronic/start.png b/packages/pinball_components/test/src/components/golden/sparky_animatronic/start.png index ea3e6344..663d8090 100644 Binary files a/packages/pinball_components/test/src/components/golden/sparky_animatronic/start.png and b/packages/pinball_components/test/src/components/golden/sparky_animatronic/start.png differ diff --git a/packages/pinball_components/test/src/components/golden/sparky_computer.png b/packages/pinball_components/test/src/components/golden/sparky_computer.png new file mode 100644 index 00000000..22fdc7e5 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/sparky_computer.png differ diff --git a/packages/pinball_components/test/src/components/google_letter/behaviors/google_letter_ball_contact_behavior_test.dart b/packages/pinball_components/test/src/components/google_letter/behaviors/google_letter_ball_contact_behavior_test.dart deleted file mode 100644 index 6a6fd437..00000000 --- a/packages/pinball_components/test/src/components/google_letter/behaviors/google_letter_ball_contact_behavior_test.dart +++ /dev/null @@ -1,55 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:bloc_test/bloc_test.dart'; -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_components/src/components/google_letter/behaviors/behaviors.dart'; - -import '../../../../helpers/helpers.dart'; - -class _MockGoogleLetterCubit extends Mock implements GoogleLetterCubit {} - -class _MockBall extends Mock implements Ball {} - -class _MockContact extends Mock implements Contact {} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(TestGame.new); - - group( - 'GoogleLetterBallContactBehavior', - () { - test('can be instantiated', () { - expect( - GoogleLetterBallContactBehavior(), - isA(), - ); - }); - - flameTester.test( - 'beginContact emits onBallContacted when contacts with a ball', - (game) async { - final behavior = GoogleLetterBallContactBehavior(); - final bloc = _MockGoogleLetterCubit(); - whenListen( - bloc, - const Stream.empty(), - initialState: GoogleLetterState.lit, - ); - - final googleLetter = GoogleLetter.test(bloc: bloc); - await googleLetter.add(behavior); - await game.ensureAdd(googleLetter); - - behavior.beginContact(_MockBall(), _MockContact()); - - verify(googleLetter.bloc.onBallContacted).called(1); - }, - ); - }, - ); -} diff --git a/packages/pinball_components/test/src/components/google_letter/cubit/google_letter_cubit_test.dart b/packages/pinball_components/test/src/components/google_letter/cubit/google_letter_cubit_test.dart deleted file mode 100644 index 812e86de..00000000 --- a/packages/pinball_components/test/src/components/google_letter/cubit/google_letter_cubit_test.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball_components/pinball_components.dart'; - -void main() { - group( - 'GoogleLetterCubit', - () { - blocTest( - 'onBallContacted emits active', - build: GoogleLetterCubit.new, - act: (bloc) => bloc.onBallContacted(), - expect: () => [GoogleLetterState.lit], - ); - - blocTest( - 'onReset emits inactive', - build: GoogleLetterCubit.new, - act: (bloc) => bloc.onReset(), - expect: () => [GoogleLetterState.dimmed], - ); - }, - ); -} diff --git a/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart b/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart deleted file mode 100644 index 1ef5e7a7..00000000 --- a/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart +++ /dev/null @@ -1,145 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:bloc_test/bloc_test.dart'; -import 'package:flame/components.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_components/src/components/google_letter/behaviors/behaviors.dart'; - -import '../../../helpers/helpers.dart'; - -class _MockGoogleLetterCubit extends Mock implements GoogleLetterCubit {} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.googleWord.letter1.lit.keyName, - Assets.images.googleWord.letter1.dimmed.keyName, - Assets.images.googleWord.letter2.lit.keyName, - Assets.images.googleWord.letter2.dimmed.keyName, - Assets.images.googleWord.letter3.lit.keyName, - Assets.images.googleWord.letter3.dimmed.keyName, - Assets.images.googleWord.letter4.lit.keyName, - Assets.images.googleWord.letter4.dimmed.keyName, - Assets.images.googleWord.letter5.lit.keyName, - Assets.images.googleWord.letter5.dimmed.keyName, - Assets.images.googleWord.letter6.lit.keyName, - Assets.images.googleWord.letter6.dimmed.keyName, - ]; - final flameTester = FlameTester(() => TestGame(assets)); - - group('Google Letter', () { - flameTester.test( - '0th loads correctly', - (game) async { - final googleLetter = GoogleLetter(0); - await game.ready(); - await game.ensureAdd(googleLetter); - - expect(game.contains(googleLetter), isTrue); - }, - ); - - flameTester.test( - '1st loads correctly', - (game) async { - final googleLetter = GoogleLetter(1); - await game.ready(); - await game.ensureAdd(googleLetter); - - expect(game.contains(googleLetter), isTrue); - }, - ); - - flameTester.test( - '2nd loads correctly', - (game) async { - final googleLetter = GoogleLetter(2); - await game.ready(); - await game.ensureAdd(googleLetter); - - expect(game.contains(googleLetter), isTrue); - }, - ); - - flameTester.test( - '3d loads correctly', - (game) async { - final googleLetter = GoogleLetter(3); - await game.ready(); - await game.ensureAdd(googleLetter); - - expect(game.contains(googleLetter), isTrue); - }, - ); - - flameTester.test( - '4th loads correctly', - (game) async { - final googleLetter = GoogleLetter(4); - await game.ready(); - await game.ensureAdd(googleLetter); - - expect(game.contains(googleLetter), isTrue); - }, - ); - - flameTester.test( - '5th loads correctly', - (game) async { - final googleLetter = GoogleLetter(5); - await game.ready(); - await game.ensureAdd(googleLetter); - - expect(game.contains(googleLetter), isTrue); - }, - ); - - test('throws error when index out of range', () { - expect(() => GoogleLetter(-1), throwsA(isA())); - expect(() => GoogleLetter(6), throwsA(isA())); - }); - - flameTester.test('closes bloc when removed', (game) async { - final bloc = _MockGoogleLetterCubit(); - whenListen( - bloc, - const Stream.empty(), - initialState: GoogleLetterState.lit, - ); - when(bloc.close).thenAnswer((_) async {}); - final googleLetter = GoogleLetter.test(bloc: bloc); - - await game.ensureAdd(googleLetter); - game.remove(googleLetter); - await game.ready(); - - verify(bloc.close).called(1); - }); - - group('adds', () { - flameTester.test('new children', (game) async { - final component = Component(); - final googleLetter = GoogleLetter( - 1, - children: [component], - ); - await game.ensureAdd(googleLetter); - expect(googleLetter.children, contains(component)); - }); - - flameTester.test('a GoogleLetterBallContactBehavior', (game) async { - final googleLetter = GoogleLetter(0); - await game.ensureAdd(googleLetter); - expect( - googleLetter.children - .whereType() - .single, - isNotNull, - ); - }); - }); - }); -} diff --git a/packages/pinball_components/test/src/components/google_letter_test.dart b/packages/pinball_components/test/src/components/google_letter_test.dart new file mode 100644 index 00000000..7deea645 --- /dev/null +++ b/packages/pinball_components/test/src/components/google_letter_test.dart @@ -0,0 +1,159 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.googleWord.letter1.lit.keyName, + Assets.images.googleWord.letter1.dimmed.keyName, + Assets.images.googleWord.letter2.lit.keyName, + Assets.images.googleWord.letter2.dimmed.keyName, + Assets.images.googleWord.letter3.lit.keyName, + Assets.images.googleWord.letter3.dimmed.keyName, + Assets.images.googleWord.letter4.lit.keyName, + Assets.images.googleWord.letter4.dimmed.keyName, + Assets.images.googleWord.letter5.lit.keyName, + Assets.images.googleWord.letter5.dimmed.keyName, + Assets.images.googleWord.letter6.lit.keyName, + Assets.images.googleWord.letter6.dimmed.keyName, + ]); + } + + Future pump(GoogleLetter child) async { + await ensureAdd( + FlameBlocProvider.value( + value: GoogleWordCubit(), + children: [child], + ), + ); + } +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final flameTester = FlameTester(_TestGame.new); + + group('Google Letter', () { + test('can be instantiated', () { + expect(GoogleLetter(0), isA()); + }); + + flameTester.test( + '0th loads correctly', + (game) async { + final googleLetter = GoogleLetter(0); + await game.pump(googleLetter); + + expect(game.descendants().contains(googleLetter), isTrue); + }, + ); + + flameTester.test( + '1st loads correctly', + (game) async { + final googleLetter = GoogleLetter(1); + await game.pump(googleLetter); + + expect(game.descendants().contains(googleLetter), isTrue); + }, + ); + + flameTester.test( + '2nd loads correctly', + (game) async { + final googleLetter = GoogleLetter(2); + await game.pump(googleLetter); + + expect(game.descendants().contains(googleLetter), isTrue); + }, + ); + + flameTester.test( + '3d loads correctly', + (game) async { + final googleLetter = GoogleLetter(3); + await game.pump(googleLetter); + + expect(game.descendants().contains(googleLetter), isTrue); + }, + ); + + flameTester.test( + '4th loads correctly', + (game) async { + final googleLetter = GoogleLetter(4); + await game.pump(googleLetter); + + expect(game.descendants().contains(googleLetter), isTrue); + }, + ); + + flameTester.test( + '5th loads correctly', + (game) async { + final googleLetter = GoogleLetter(5); + await game.pump(googleLetter); + + expect(game.descendants().contains(googleLetter), isTrue); + }, + ); + + test('throws error when index out of range', () { + expect(() => GoogleLetter(-1), throwsA(isA())); + expect(() => GoogleLetter(6), throwsA(isA())); + }); + + group('sprite', () { + const firstLetterLitState = GoogleWordState( + letterSpriteStates: { + 0: GoogleLetterSpriteState.lit, + 1: GoogleLetterSpriteState.dimmed, + 2: GoogleLetterSpriteState.dimmed, + 3: GoogleLetterSpriteState.dimmed, + 4: GoogleLetterSpriteState.dimmed, + 5: GoogleLetterSpriteState.dimmed, + }, + ); + + flameTester.test( + "listens when its index's state changes", + (game) async { + final googleLetter = GoogleLetter(0); + await game.pump(googleLetter); + + expect( + googleLetter.listenWhen( + GoogleWordState.initial(), + firstLetterLitState, + ), + isTrue, + ); + }, + ); + + flameTester.test( + 'changes current sprite onNewState', + (game) async { + final googleLetter = GoogleLetter(0); + await game.pump(googleLetter); + + final originalSprite = googleLetter.current; + + googleLetter.onNewState(firstLetterLitState); + await game.ready(); + + final newSprite = googleLetter.current; + expect(newSprite != originalSprite, isTrue); + }, + ); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/google_rollover/behaviors/google_rollover_ball_contact_behavior_test.dart b/packages/pinball_components/test/src/components/google_rollover/behaviors/google_rollover_ball_contact_behavior_test.dart new file mode 100644 index 00000000..9d2e6fdf --- /dev/null +++ b/packages/pinball_components/test/src/components/google_rollover/behaviors/google_rollover_ball_contact_behavior_test.dart @@ -0,0 +1,81 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/google_rollover/behaviors/behaviors.dart'; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.googleRollover.left.decal.keyName, + Assets.images.googleRollover.left.pin.keyName, + ]); + } + + Future pump( + GoogleRollover child, { + GoogleWordCubit? bloc, + }) async { + // Not needed once https://github.com/flame-engine/flame/issues/1607 + // is fixed + await onLoad(); + await ensureAdd( + FlameBlocProvider.value( + value: bloc ?? GoogleWordCubit(), + children: [child], + ), + ); + } +} + +class _MockBall extends Mock implements Ball {} + +class _MockContact extends Mock implements Contact {} + +class _MockGoogleWordCubit extends Mock implements GoogleWordCubit {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final flameTester = FlameTester(_TestGame.new); + + group( + 'GoogleRolloverBallContactBehavior', + () { + test('can be instantiated', () { + expect( + GoogleRolloverBallContactBehavior(), + isA(), + ); + }); + + flameTester.testGameWidget( + 'beginContact animates pin and calls onRolloverContacted ' + 'when contacts with a ball', + setUp: (game, tester) async { + final behavior = GoogleRolloverBallContactBehavior(); + final bloc = _MockGoogleWordCubit(); + final googleRollover = GoogleRollover(side: BoardSide.left); + await googleRollover.add(behavior); + await game.pump(googleRollover, bloc: bloc); + + behavior.beginContact(_MockBall(), _MockContact()); + await tester.pump(); + + expect( + googleRollover.firstChild()!.playing, + isTrue, + ); + verify(bloc.onRolloverContacted).called(1); + }, + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/google_rollover/google_rollover_test.dart b/packages/pinball_components/test/src/components/google_rollover/google_rollover_test.dart new file mode 100644 index 00000000..199803a0 --- /dev/null +++ b/packages/pinball_components/test/src/components/google_rollover/google_rollover_test.dart @@ -0,0 +1,82 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/google_rollover/behaviors/behaviors.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + Assets.images.googleRollover.left.decal.keyName, + Assets.images.googleRollover.left.pin.keyName, + Assets.images.googleRollover.right.decal.keyName, + Assets.images.googleRollover.right.pin.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); + + group('GoogleRollover', () { + test('can be instantiated', () { + expect( + GoogleRollover(side: BoardSide.left), + isA(), + ); + }); + + flameTester.test('left loads correctly', (game) async { + final googleRollover = GoogleRollover(side: BoardSide.left); + await game.ensureAdd(googleRollover); + expect(game.contains(googleRollover), isTrue); + }); + + flameTester.test('right loads correctly', (game) async { + final googleRollover = GoogleRollover(side: BoardSide.right); + await game.ensureAdd(googleRollover); + expect(game.contains(googleRollover), isTrue); + }); + + group('adds', () { + flameTester.test('new children', (game) async { + final component = Component(); + final googleRollover = GoogleRollover( + side: BoardSide.left, + children: [component], + ); + await game.ensureAdd(googleRollover); + expect(googleRollover.children, contains(component)); + }); + + flameTester.test('a GoogleRolloverBallContactBehavior', (game) async { + final googleRollover = GoogleRollover(side: BoardSide.left); + await game.ensureAdd(googleRollover); + expect( + googleRollover.children + .whereType() + .single, + isNotNull, + ); + }); + }); + + flameTester.test( + 'pin stops animating after animation completes', + (game) async { + final googleRollover = GoogleRollover(side: BoardSide.left); + await game.ensureAdd(googleRollover); + + final pinSpriteAnimationComponent = + googleRollover.firstChild()!; + + pinSpriteAnimationComponent.playing = true; + game.update( + pinSpriteAnimationComponent.animation!.totalDuration() + 0.1, + ); + + expect(pinSpriteAnimationComponent.playing, isFalse); + }, + ); + }); +} diff --git a/packages/pinball_components/test/src/components/google_word/cubit/google_word_cubit_test.dart b/packages/pinball_components/test/src/components/google_word/cubit/google_word_cubit_test.dart new file mode 100644 index 00000000..08acfae8 --- /dev/null +++ b/packages/pinball_components/test/src/components/google_word/cubit/google_word_cubit_test.dart @@ -0,0 +1,35 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group( + 'GoogleWordCubit', + () { + blocTest( + 'onRolloverContacted emits first letter lit', + build: GoogleWordCubit.new, + act: (bloc) => bloc.onRolloverContacted(), + expect: () => [ + const GoogleWordState( + letterSpriteStates: { + 0: GoogleLetterSpriteState.lit, + 1: GoogleLetterSpriteState.dimmed, + 2: GoogleLetterSpriteState.dimmed, + 3: GoogleLetterSpriteState.dimmed, + 4: GoogleLetterSpriteState.dimmed, + 5: GoogleLetterSpriteState.dimmed, + }, + ), + ], + ); + + blocTest( + 'onBonusAwarded emits initial state', + build: GoogleWordCubit.new, + act: (bloc) => bloc.onBonusAwarded(), + expect: () => [GoogleWordState.initial()], + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/google_word/cubit/google_word_state_test.dart b/packages/pinball_components/test/src/components/google_word/cubit/google_word_state_test.dart new file mode 100644 index 00000000..6195c785 --- /dev/null +++ b/packages/pinball_components/test/src/components/google_word/cubit/google_word_state_test.dart @@ -0,0 +1,58 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group('GoogleWordState', () { + test('supports value equality', () { + expect( + GoogleWordState( + letterSpriteStates: const { + 0: GoogleLetterSpriteState.dimmed, + 1: GoogleLetterSpriteState.dimmed, + 2: GoogleLetterSpriteState.dimmed, + 3: GoogleLetterSpriteState.dimmed, + 4: GoogleLetterSpriteState.dimmed, + 5: GoogleLetterSpriteState.dimmed, + }, + ), + equals( + GoogleWordState( + letterSpriteStates: const { + 0: GoogleLetterSpriteState.dimmed, + 1: GoogleLetterSpriteState.dimmed, + 2: GoogleLetterSpriteState.dimmed, + 3: GoogleLetterSpriteState.dimmed, + 4: GoogleLetterSpriteState.dimmed, + 5: GoogleLetterSpriteState.dimmed, + }, + ), + ), + ); + }); + + group('constructor', () { + test('can be instantiated', () { + expect( + const GoogleWordState(letterSpriteStates: {}), + isNotNull, + ); + }); + + test('initial has all dimmed sprite states', () { + const initialState = GoogleWordState( + letterSpriteStates: { + 0: GoogleLetterSpriteState.dimmed, + 1: GoogleLetterSpriteState.dimmed, + 2: GoogleLetterSpriteState.dimmed, + 3: GoogleLetterSpriteState.dimmed, + 4: GoogleLetterSpriteState.dimmed, + 5: GoogleLetterSpriteState.dimmed, + }, + ); + expect(GoogleWordState.initial(), equals(initialState)); + }); + }); + }); +} diff --git a/test/game/components/google_word/google_word_test.dart b/packages/pinball_components/test/src/components/google_word/google_word_test.dart similarity index 65% rename from test/game/components/google_word/google_word_test.dart rename to packages/pinball_components/test/src/components/google_word/google_word_test.dart index c0258281..daee7d37 100644 --- a/test/game/components/google_word/google_word_test.dart +++ b/packages/pinball_components/test/src/components/google_word/google_word_test.dart @@ -4,8 +4,6 @@ import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball/game/components/google_word/behaviors/behaviors.dart'; -import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; class _TestGame extends Forge2DGame { @@ -28,10 +26,10 @@ class _TestGame extends Forge2DGame { ]); } - Future pump(GoogleWord child, {GameBloc? gameBloc}) { - return ensureAdd( - FlameBlocProvider.value( - value: gameBloc ?? GameBloc(), + Future pump(GoogleWord child) async { + await ensureAdd( + FlameBlocProvider.value( + value: GoogleWordCubit(), children: [child], ), ); @@ -44,25 +42,21 @@ void main() { final flameTester = FlameTester(_TestGame.new); group('GoogleWord', () { + test('can be instantiated', () { + expect(GoogleWord(position: Vector2.zero()), isA()); + }); + flameTester.test( - 'loads the letters correctly', + 'loads letters correctly', (game) async { - const word = 'Google'; final googleWord = GoogleWord(position: Vector2.zero()); await game.pump(googleWord); - final letters = googleWord.children.whereType(); - expect(letters.length, equals(word.length)); + expect( + googleWord.children.whereType().length, + equals(6), + ); }, ); - - flameTester.test('adds a GoogleWordBonusBehavior', (game) async { - final googleWord = GoogleWord(position: Vector2.zero()); - await game.pump(googleWord); - expect( - googleWord.children.whereType().single, - isNotNull, - ); - }); }); } diff --git a/packages/pinball_components/test/src/components/launch_ramp_test.dart b/packages/pinball_components/test/src/components/launch_ramp_test.dart index 38c0920b..11033b5a 100644 --- a/packages/pinball_components/test/src/components/launch_ramp_test.dart +++ b/packages/pinball_components/test/src/components/launch_ramp_test.dart @@ -36,7 +36,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('golden/launch-ramp.png'), + matchesGoldenFile('golden/launch_ramp.png'), ); }, ); diff --git a/packages/pinball_components/test/src/components/multiball/behaviors/multiball_blinking_behavior_test.dart b/packages/pinball_components/test/src/components/multiball/behaviors/multiball_blinking_behavior_test.dart index 379f8610..0fcf7668 100644 --- a/packages/pinball_components/test/src/components/multiball/behaviors/multiball_blinking_behavior_test.dart +++ b/packages/pinball_components/test/src/components/multiball/behaviors/multiball_blinking_behavior_test.dart @@ -21,7 +21,7 @@ void main() { 'MultiballBlinkingBehavior', () { flameTester.testGameWidget( - 'calls onBlink every 0.1 seconds when animation state is animated', + 'calls onBlink every 0.18 seconds when animation state is animated', setUp: (game, tester) async { final behavior = MultiballBlinkingBehavior(); final bloc = _MockMultiballCubit(); @@ -48,7 +48,7 @@ void main() { verify(bloc.onBlink).called(1); await tester.pump(); - game.update(0.1); + game.update(0.18); await streamController.close(); verify(bloc.onBlink).called(1); @@ -124,7 +124,7 @@ void main() { ); flameTester.testGameWidget( - 'onTick stops after 10 blinks repetitions', + 'onTick stops after 28 blinks', setUp: (game, tester) async { final behavior = MultiballBlinkingBehavior(); final bloc = _MockMultiballCubit(); @@ -148,7 +148,7 @@ void main() { ); await tester.pump(); - for (var i = 0; i < 10; i++) { + for (var i = 0; i < 28; i++) { behavior.onTick(); } diff --git a/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart b/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart index c612ecb9..0a28ae54 100644 --- a/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart +++ b/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart @@ -122,7 +122,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x2-lit.png'), + matchesGoldenFile('../golden/multipliers/x2_lit.png'), ); }, ); @@ -162,7 +162,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x2-dimmed.png'), + matchesGoldenFile('../golden/multipliers/x2_dimmed.png'), ); }, ); @@ -206,7 +206,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x3-lit.png'), + matchesGoldenFile('../golden/multipliers/x3_lit.png'), ); }, ); @@ -246,7 +246,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x3-dimmed.png'), + matchesGoldenFile('../golden/multipliers/x3_dimmed.png'), ); }, ); @@ -290,7 +290,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x4-lit.png'), + matchesGoldenFile('../golden/multipliers/x4_lit.png'), ); }, ); @@ -330,7 +330,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x4-dimmed.png'), + matchesGoldenFile('../golden/multipliers/x4_dimmed.png'), ); }, ); @@ -374,7 +374,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x5-lit.png'), + matchesGoldenFile('../golden/multipliers/x5_lit.png'), ); }, ); @@ -414,7 +414,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x5-dimmed.png'), + matchesGoldenFile('../golden/multipliers/x5_dimmed.png'), ); }, ); @@ -458,7 +458,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x6-lit.png'), + matchesGoldenFile('../golden/multipliers/x6_lit.png'), ); }, ); @@ -498,7 +498,7 @@ void main() { await expectLater( find.byGame<_TestGame>(), - matchesGoldenFile('../golden/multipliers/x6-dimmed.png'), + matchesGoldenFile('../golden/multipliers/x6_dimmed.png'), ); }, ); diff --git a/packages/pinball_components/test/src/components/shapes/arc_shape_test.dart b/packages/pinball_components/test/src/components/shapes/arc_shape_test.dart deleted file mode 100644 index fe778872..00000000 --- a/packages/pinball_components/test/src/components/shapes/arc_shape_test.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'dart:math' as math; -import 'package:flame/extensions.dart'; -import 'package:flame/game.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball_components/src/components/components.dart'; - -void main() { - group('ArcShape', () { - test('can be instantiated', () { - expect( - ArcShape( - center: Vector2.zero(), - arcRadius: 10, - angle: 2 * math.pi, - ), - isNotNull, - ); - }); - - group('copyWith', () { - test( - 'copies correctly ' - 'when no argument specified', () { - final arcShape = ArcShape( - center: Vector2.zero(), - arcRadius: 10, - angle: 2 * math.pi, - ); - final arcShapeCopied = arcShape.copyWith(); - - for (var index = 0; index < arcShape.vertices.length; index++) { - expect( - arcShape.vertices[index], - equals(arcShapeCopied.vertices[index]), - ); - } - }); - - test( - 'copies correctly ' - 'when all arguments specified', () { - final arcShapeExpected = ArcShape( - center: Vector2.all(10), - arcRadius: 15, - angle: 2 * math.pi, - ); - final arcShapeCopied = ArcShape( - center: Vector2.zero(), - arcRadius: 10, - angle: math.pi, - ).copyWith( - center: Vector2.all(10), - arcRadius: 15, - angle: 2 * math.pi, - ); - - for (var index = 0; index < arcShapeCopied.vertices.length; index++) { - expect( - arcShapeCopied.vertices[index], - equals(arcShapeExpected.vertices[index]), - ); - } - }); - }); - }); -} diff --git a/packages/pinball_components/test/src/components/shapes/bezier_curve_shape_test.dart b/packages/pinball_components/test/src/components/shapes/bezier_curve_shape_test.dart deleted file mode 100644 index 6a6adeb7..00000000 --- a/packages/pinball_components/test/src/components/shapes/bezier_curve_shape_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'dart:math' as math; -import 'package:flame/extensions.dart'; -import 'package:flame/game.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball_components/src/components/components.dart'; - -void main() { - group('BezierCurveShape', () { - final controlPoints = [ - Vector2(0, 0), - Vector2(10, 0), - Vector2(0, 10), - Vector2(10, 10), - ]; - - test('can be instantiated', () { - expect( - BezierCurveShape( - controlPoints: controlPoints, - ), - isNotNull, - ); - }); - - group('rotate', () { - test('returns vertices rotated', () { - const rotationAngle = 2 * math.pi; - final controlPoints = [ - Vector2(0, 0), - Vector2(10, 0), - Vector2(0, 10), - Vector2(10, 10), - ]; - - final bezierCurveShape = BezierCurveShape( - controlPoints: controlPoints, - ); - final bezierCurveShapeRotated = BezierCurveShape( - controlPoints: controlPoints, - )..rotate(rotationAngle); - - for (var index = 0; index < bezierCurveShape.vertices.length; index++) { - expect( - bezierCurveShape.vertices[index]..rotate(rotationAngle), - equals(bezierCurveShapeRotated.vertices[index]), - ); - } - }); - }); - }); -} diff --git a/packages/pinball_components/test/src/components/shapes/ellipse_shape_test.dart b/packages/pinball_components/test/src/components/shapes/ellipse_shape_test.dart deleted file mode 100644 index 31f45cc1..00000000 --- a/packages/pinball_components/test/src/components/shapes/ellipse_shape_test.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'dart:math' as math; -import 'package:flame/extensions.dart'; -import 'package:flame/game.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball_components/src/components/components.dart'; - -void main() { - group('EllipseShape', () { - test('can be instantiated', () { - expect( - EllipseShape( - center: Vector2.zero(), - majorRadius: 10, - minorRadius: 8, - ), - isNotNull, - ); - }); - - group('rotate', () { - test('returns vertices rotated', () { - const rotationAngle = 2 * math.pi; - final ellipseShape = EllipseShape( - center: Vector2.zero(), - majorRadius: 10, - minorRadius: 8, - ); - final ellipseShapeRotated = EllipseShape( - center: Vector2.zero(), - majorRadius: 10, - minorRadius: 8, - )..rotate(rotationAngle); - - for (var index = 0; index < ellipseShape.vertices.length; index++) { - expect( - ellipseShape.vertices[index]..rotate(rotationAngle), - equals(ellipseShapeRotated.vertices[index]), - ); - } - }); - }); - - group('copyWith', () { - test('returns same shape when no properties are passed', () { - final ellipseShape = EllipseShape( - center: Vector2.zero(), - majorRadius: 10, - minorRadius: 8, - ); - final ellipseShapeCopied = ellipseShape.copyWith(); - - for (var index = 0; index < ellipseShape.vertices.length; index++) { - expect( - ellipseShape.vertices[index], - equals(ellipseShapeCopied.vertices[index]), - ); - } - }); - - test('returns object with updated properties when are passed', () { - final ellipseShapeExpected = EllipseShape( - center: Vector2.all(10), - majorRadius: 10, - minorRadius: 8, - ); - final ellipseShapeCopied = EllipseShape( - center: Vector2.zero(), - majorRadius: 10, - minorRadius: 8, - ).copyWith(center: Vector2.all(10)); - - for (var index = 0; - index < ellipseShapeCopied.vertices.length; - index++) { - expect( - ellipseShapeCopied.vertices[index], - equals(ellipseShapeExpected.vertices[index]), - ); - } - }); - }); - }); -} diff --git a/packages/pinball_components/test/src/components/skill_shot/cubit/skill_shot_state_test.dart b/packages/pinball_components/test/src/components/skill_shot/cubit/skill_shot_state_test.dart index ee6e3e0d..b5aca68c 100644 --- a/packages/pinball_components/test/src/components/skill_shot/cubit/skill_shot_state_test.dart +++ b/packages/pinball_components/test/src/components/skill_shot/cubit/skill_shot_state_test.dart @@ -31,7 +31,7 @@ void main() { ); }); - test('initial is idle with mouth closed', () { + test('initial is dimmed and not blinking', () { const initialState = SkillShotState( spriteState: SkillShotSpriteState.dimmed, isBlinking: false, @@ -45,13 +45,13 @@ void main() { 'copies correctly ' 'when no argument specified', () { - const chromeDinoState = SkillShotState( + const skillShotState = SkillShotState( spriteState: SkillShotSpriteState.lit, isBlinking: true, ); expect( - chromeDinoState.copyWith(), - equals(chromeDinoState), + skillShotState.copyWith(), + equals(skillShotState), ); }, ); @@ -60,7 +60,7 @@ void main() { 'copies correctly ' 'when all arguments specified', () { - const chromeDinoState = SkillShotState( + const skillShotState = SkillShotState( spriteState: SkillShotSpriteState.lit, isBlinking: true, ); @@ -68,10 +68,10 @@ void main() { spriteState: SkillShotSpriteState.dimmed, isBlinking: false, ); - expect(chromeDinoState, isNot(equals(otherSkillShotState))); + expect(skillShotState, isNot(equals(otherSkillShotState))); expect( - chromeDinoState.copyWith( + skillShotState.copyWith( spriteState: SkillShotSpriteState.dimmed, isBlinking: false, ), diff --git a/packages/pinball_components/test/src/components/spaceship_rail_test.dart b/packages/pinball_components/test/src/components/spaceship_rail_test.dart index 65e9dbd7..78aa4aaa 100644 --- a/packages/pinball_components/test/src/components/spaceship_rail_test.dart +++ b/packages/pinball_components/test/src/components/spaceship_rail_test.dart @@ -35,7 +35,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('golden/spaceship-rail.png'), + matchesGoldenFile('golden/spaceship_rail.png'), ); }, ); diff --git a/packages/pinball_components/test/src/components/sparky_computer/sparky_computer_test.dart b/packages/pinball_components/test/src/components/sparky_computer/sparky_computer_test.dart index d15f8056..0c045cb1 100644 --- a/packages/pinball_components/test/src/components/sparky_computer/sparky_computer_test.dart +++ b/packages/pinball_components/test/src/components/sparky_computer/sparky_computer_test.dart @@ -42,7 +42,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('../golden/sparky-computer.png'), + matchesGoldenFile('../golden/sparky_computer.png'), ); }, ); diff --git a/packages/pinball_flame/lib/pinball_flame.dart b/packages/pinball_flame/lib/pinball_flame.dart index 867fa6ba..410d3151 100644 --- a/packages/pinball_flame/lib/pinball_flame.dart +++ b/packages/pinball_flame/lib/pinball_flame.dart @@ -7,4 +7,5 @@ export 'src/keyboard_input_controller.dart'; export 'src/layer.dart'; export 'src/parent_is_a.dart'; export 'src/pinball_forge2d_game.dart'; +export 'src/shapes/shapes.dart'; export 'src/sprite_animation.dart'; diff --git a/packages/pinball_flame/lib/src/shapes/arc_shape.dart b/packages/pinball_flame/lib/src/shapes/arc_shape.dart index 8b137891..780fa2d3 100644 --- a/packages/pinball_flame/lib/src/shapes/arc_shape.dart +++ b/packages/pinball_flame/lib/src/shapes/arc_shape.dart @@ -1 +1,38 @@ +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:geometry/geometry.dart'; +/// {@template arc_shape} +/// Creates an arc. +/// {@endtemplate} +class ArcShape extends ChainShape { + /// {@macro arc_shape} + ArcShape({ + required this.center, + required this.arcRadius, + required this.angle, + this.rotation = 0, + }) { + createChain( + calculateArc( + center: center, + radius: arcRadius, + angle: angle, + offsetAngle: rotation, + ), + ); + } + + /// The center of the arc. + final Vector2 center; + + /// The radius of the arc. + final double arcRadius; + + /// Specifies the size of the arc, in radians. + /// + /// For example, two pi returns a complete circumference. + final double angle; + + /// Angle in radians to rotate the arc around its [center]. + final double rotation; +} diff --git a/packages/pinball_components/lib/src/components/shapes/bezier_curve_shape.dart b/packages/pinball_flame/lib/src/shapes/bezier_curve_shape.dart similarity index 75% rename from packages/pinball_components/lib/src/components/shapes/bezier_curve_shape.dart rename to packages/pinball_flame/lib/src/shapes/bezier_curve_shape.dart index 5fcf9e08..00d1bafb 100644 --- a/packages/pinball_components/lib/src/components/shapes/bezier_curve_shape.dart +++ b/packages/pinball_flame/lib/src/shapes/bezier_curve_shape.dart @@ -1,4 +1,3 @@ -import 'package:flame/extensions.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:geometry/geometry.dart'; @@ -18,9 +17,4 @@ class BezierCurveShape extends ChainShape { /// First and last [controlPoints] set the beginning and end of the curve, /// inner points between them set its final shape. final List controlPoints; - - /// Rotates the bezier curve by a given [angle] in radians. - void rotate(double angle) { - vertices.map((vector) => vector..rotate(angle)).toList(); - } } diff --git a/packages/pinball_components/lib/src/components/shapes/ellipse_shape.dart b/packages/pinball_flame/lib/src/shapes/ellipse_shape.dart similarity index 72% rename from packages/pinball_components/lib/src/components/shapes/ellipse_shape.dart rename to packages/pinball_flame/lib/src/shapes/ellipse_shape.dart index 488d3d6f..5c523a3f 100644 --- a/packages/pinball_components/lib/src/components/shapes/ellipse_shape.dart +++ b/packages/pinball_flame/lib/src/shapes/ellipse_shape.dart @@ -34,17 +34,8 @@ class EllipseShape extends ChainShape { /// Rotates the ellipse by a given [angle] in radians. void rotate(double angle) { - vertices.map((vector) => vector..rotate(angle)).toList(); + for (final vector in vertices) { + vector.rotate(angle); + } } - - EllipseShape copyWith({ - Vector2? center, - double? majorRadius, - double? minorRadius, - }) => - EllipseShape( - center: center ?? this.center, - majorRadius: majorRadius ?? this.majorRadius, - minorRadius: minorRadius ?? this.minorRadius, - ); } diff --git a/packages/pinball_components/lib/src/components/shapes/shapes.dart b/packages/pinball_flame/lib/src/shapes/shapes.dart similarity index 100% rename from packages/pinball_components/lib/src/components/shapes/shapes.dart rename to packages/pinball_flame/lib/src/shapes/shapes.dart diff --git a/packages/pinball_flame/pubspec.yaml b/packages/pinball_flame/pubspec.yaml index 4639a080..327951a1 100644 --- a/packages/pinball_flame/pubspec.yaml +++ b/packages/pinball_flame/pubspec.yaml @@ -17,6 +17,8 @@ dependencies: ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f flutter: sdk: flutter + geometry: + path: ../geometry dev_dependencies: flame_test: ^1.3.0 diff --git a/packages/pinball_flame/test/src/shapes/arc_shape_test.dart b/packages/pinball_flame/test/src/shapes/arc_shape_test.dart new file mode 100644 index 00000000..0c9f0a0f --- /dev/null +++ b/packages/pinball_flame/test/src/shapes/arc_shape_test.dart @@ -0,0 +1,20 @@ +import 'dart:math' as math; +import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +void main() { + group('ArcShape', () { + test('can be instantiated', () { + expect( + ArcShape( + center: Vector2.zero(), + arcRadius: 10, + angle: 2 * math.pi, + ), + isA(), + ); + }); + }); +} diff --git a/packages/pinball_flame/test/src/shapes/bezier_curve_shape_test.dart b/packages/pinball_flame/test/src/shapes/bezier_curve_shape_test.dart new file mode 100644 index 00000000..c2328a2f --- /dev/null +++ b/packages/pinball_flame/test/src/shapes/bezier_curve_shape_test.dart @@ -0,0 +1,22 @@ +import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +void main() { + group('BezierCurveShape', () { + test('can be instantiated', () { + expect( + BezierCurveShape( + controlPoints: [ + Vector2(0, 0), + Vector2(10, 0), + Vector2(0, 10), + Vector2(10, 10), + ], + ), + isA(), + ); + }); + }); +} diff --git a/packages/pinball_flame/test/src/shapes/ellipse_shape_test.dart b/packages/pinball_flame/test/src/shapes/ellipse_shape_test.dart new file mode 100644 index 00000000..0bb760d9 --- /dev/null +++ b/packages/pinball_flame/test/src/shapes/ellipse_shape_test.dart @@ -0,0 +1,41 @@ +import 'dart:math' as math; +import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +void main() { + group('EllipseShape', () { + test('can be instantiated', () { + expect( + EllipseShape( + center: Vector2.zero(), + majorRadius: 10, + minorRadius: 8, + ), + isA(), + ); + }); + + test('rotate returns vertices rotated', () { + const rotationAngle = 2 * math.pi; + final ellipseShape = EllipseShape( + center: Vector2.zero(), + majorRadius: 10, + minorRadius: 8, + ); + final ellipseShapeRotated = EllipseShape( + center: Vector2.zero(), + majorRadius: 10, + minorRadius: 8, + )..rotate(rotationAngle); + + for (var index = 0; index < ellipseShape.vertices.length; index++) { + expect( + ellipseShape.vertices[index]..rotate(rotationAngle), + equals(ellipseShapeRotated.vertices[index]), + ); + } + }); + }); +} diff --git a/packages/pinball_theme/assets/images/android/animation.png b/packages/pinball_theme/assets/images/android/animation.png index fc7465be..47f5f74c 100644 Binary files a/packages/pinball_theme/assets/images/android/animation.png and b/packages/pinball_theme/assets/images/android/animation.png differ diff --git a/packages/pinball_theme/assets/images/android/background.jpg b/packages/pinball_theme/assets/images/android/background.jpg new file mode 100644 index 00000000..afe2e3c6 Binary files /dev/null and b/packages/pinball_theme/assets/images/android/background.jpg differ diff --git a/packages/pinball_theme/assets/images/android/background.png b/packages/pinball_theme/assets/images/android/background.png deleted file mode 100644 index f751dcdc..00000000 Binary files a/packages/pinball_theme/assets/images/android/background.png and /dev/null differ diff --git a/packages/pinball_theme/assets/images/android/ball.png b/packages/pinball_theme/assets/images/android/ball.png index ca2eebe3..8310c098 100644 Binary files a/packages/pinball_theme/assets/images/android/ball.png and b/packages/pinball_theme/assets/images/android/ball.png differ diff --git a/packages/pinball_theme/assets/images/android/icon.png b/packages/pinball_theme/assets/images/android/icon.png index ff365ffe..03857b0a 100644 Binary files a/packages/pinball_theme/assets/images/android/icon.png and b/packages/pinball_theme/assets/images/android/icon.png differ diff --git a/packages/pinball_theme/assets/images/android/leaderboard_icon.png b/packages/pinball_theme/assets/images/android/leaderboard_icon.png index 238e29ef..ae97c412 100644 Binary files a/packages/pinball_theme/assets/images/android/leaderboard_icon.png and b/packages/pinball_theme/assets/images/android/leaderboard_icon.png differ diff --git a/packages/pinball_theme/assets/images/dash/animation.png b/packages/pinball_theme/assets/images/dash/animation.png index e812415f..97d4fb07 100644 Binary files a/packages/pinball_theme/assets/images/dash/animation.png and b/packages/pinball_theme/assets/images/dash/animation.png differ diff --git a/packages/pinball_theme/assets/images/dash/background.jpg b/packages/pinball_theme/assets/images/dash/background.jpg new file mode 100644 index 00000000..0b70a795 Binary files /dev/null and b/packages/pinball_theme/assets/images/dash/background.jpg differ diff --git a/packages/pinball_theme/assets/images/dash/background.png b/packages/pinball_theme/assets/images/dash/background.png deleted file mode 100644 index a36601c9..00000000 Binary files a/packages/pinball_theme/assets/images/dash/background.png and /dev/null differ diff --git a/packages/pinball_theme/assets/images/dash/ball.png b/packages/pinball_theme/assets/images/dash/ball.png index 69e8dffa..f93023f6 100644 Binary files a/packages/pinball_theme/assets/images/dash/ball.png and b/packages/pinball_theme/assets/images/dash/ball.png differ diff --git a/packages/pinball_theme/assets/images/dash/icon.png b/packages/pinball_theme/assets/images/dash/icon.png index 45bba327..0df550ea 100644 Binary files a/packages/pinball_theme/assets/images/dash/icon.png and b/packages/pinball_theme/assets/images/dash/icon.png differ diff --git a/packages/pinball_theme/assets/images/dash/leaderboard_icon.png b/packages/pinball_theme/assets/images/dash/leaderboard_icon.png index 5c172d47..69b2919c 100644 Binary files a/packages/pinball_theme/assets/images/dash/leaderboard_icon.png and b/packages/pinball_theme/assets/images/dash/leaderboard_icon.png differ diff --git a/packages/pinball_theme/assets/images/dino/animation.png b/packages/pinball_theme/assets/images/dino/animation.png index c75b16f9..de7e1b60 100644 Binary files a/packages/pinball_theme/assets/images/dino/animation.png and b/packages/pinball_theme/assets/images/dino/animation.png differ diff --git a/packages/pinball_theme/assets/images/dino/background.jpg b/packages/pinball_theme/assets/images/dino/background.jpg new file mode 100644 index 00000000..35deff95 Binary files /dev/null and b/packages/pinball_theme/assets/images/dino/background.jpg differ diff --git a/packages/pinball_theme/assets/images/dino/background.png b/packages/pinball_theme/assets/images/dino/background.png deleted file mode 100644 index e42f1705..00000000 Binary files a/packages/pinball_theme/assets/images/dino/background.png and /dev/null differ diff --git a/packages/pinball_theme/assets/images/dino/ball.png b/packages/pinball_theme/assets/images/dino/ball.png index 1a2102d8..7c1885a2 100644 Binary files a/packages/pinball_theme/assets/images/dino/ball.png and b/packages/pinball_theme/assets/images/dino/ball.png differ diff --git a/packages/pinball_theme/assets/images/dino/icon.png b/packages/pinball_theme/assets/images/dino/icon.png index 0114060e..28682c73 100644 Binary files a/packages/pinball_theme/assets/images/dino/icon.png and b/packages/pinball_theme/assets/images/dino/icon.png differ diff --git a/packages/pinball_theme/assets/images/dino/leaderboard_icon.png b/packages/pinball_theme/assets/images/dino/leaderboard_icon.png index b1033371..4a230b39 100644 Binary files a/packages/pinball_theme/assets/images/dino/leaderboard_icon.png and b/packages/pinball_theme/assets/images/dino/leaderboard_icon.png differ diff --git a/packages/pinball_theme/assets/images/pinball_button.png b/packages/pinball_theme/assets/images/pinball_button.png index 62373b85..8d10ec7c 100644 Binary files a/packages/pinball_theme/assets/images/pinball_button.png and b/packages/pinball_theme/assets/images/pinball_button.png differ diff --git a/packages/pinball_theme/assets/images/select_character_background.png b/packages/pinball_theme/assets/images/select_character_background.png index 69120148..7ba7f17f 100644 Binary files a/packages/pinball_theme/assets/images/select_character_background.png and b/packages/pinball_theme/assets/images/select_character_background.png differ diff --git a/packages/pinball_theme/assets/images/sparky/animation.png b/packages/pinball_theme/assets/images/sparky/animation.png index 1aff4772..ce51eb0d 100644 Binary files a/packages/pinball_theme/assets/images/sparky/animation.png and b/packages/pinball_theme/assets/images/sparky/animation.png differ diff --git a/packages/pinball_theme/assets/images/sparky/background.jpg b/packages/pinball_theme/assets/images/sparky/background.jpg new file mode 100644 index 00000000..9c4fdfc6 Binary files /dev/null and b/packages/pinball_theme/assets/images/sparky/background.jpg differ diff --git a/packages/pinball_theme/assets/images/sparky/background.png b/packages/pinball_theme/assets/images/sparky/background.png deleted file mode 100644 index 5044376c..00000000 Binary files a/packages/pinball_theme/assets/images/sparky/background.png and /dev/null differ diff --git a/packages/pinball_theme/assets/images/sparky/ball.png b/packages/pinball_theme/assets/images/sparky/ball.png index fe3f4e82..7fd20368 100644 Binary files a/packages/pinball_theme/assets/images/sparky/ball.png and b/packages/pinball_theme/assets/images/sparky/ball.png differ diff --git a/packages/pinball_theme/assets/images/sparky/icon.png b/packages/pinball_theme/assets/images/sparky/icon.png index 4e484438..46b8dfa3 100644 Binary files a/packages/pinball_theme/assets/images/sparky/icon.png and b/packages/pinball_theme/assets/images/sparky/icon.png differ diff --git a/packages/pinball_theme/assets/images/sparky/leaderboard_icon.png b/packages/pinball_theme/assets/images/sparky/leaderboard_icon.png index 76001516..bd5e56e1 100644 Binary files a/packages/pinball_theme/assets/images/sparky/leaderboard_icon.png and b/packages/pinball_theme/assets/images/sparky/leaderboard_icon.png differ diff --git a/packages/pinball_theme/lib/src/generated/assets.gen.dart b/packages/pinball_theme/lib/src/generated/assets.gen.dart index 545f514b..17ec4caf 100644 --- a/packages/pinball_theme/lib/src/generated/assets.gen.dart +++ b/packages/pinball_theme/lib/src/generated/assets.gen.dart @@ -32,9 +32,9 @@ class $AssetsImagesAndroidGen { AssetGenImage get animation => const AssetGenImage('assets/images/android/animation.png'); - /// File path: assets/images/android/background.png + /// File path: assets/images/android/background.jpg AssetGenImage get background => - const AssetGenImage('assets/images/android/background.png'); + const AssetGenImage('assets/images/android/background.jpg'); /// File path: assets/images/android/ball.png AssetGenImage get ball => @@ -56,9 +56,9 @@ class $AssetsImagesDashGen { AssetGenImage get animation => const AssetGenImage('assets/images/dash/animation.png'); - /// File path: assets/images/dash/background.png + /// File path: assets/images/dash/background.jpg AssetGenImage get background => - const AssetGenImage('assets/images/dash/background.png'); + const AssetGenImage('assets/images/dash/background.jpg'); /// File path: assets/images/dash/ball.png AssetGenImage get ball => const AssetGenImage('assets/images/dash/ball.png'); @@ -78,9 +78,9 @@ class $AssetsImagesDinoGen { AssetGenImage get animation => const AssetGenImage('assets/images/dino/animation.png'); - /// File path: assets/images/dino/background.png + /// File path: assets/images/dino/background.jpg AssetGenImage get background => - const AssetGenImage('assets/images/dino/background.png'); + const AssetGenImage('assets/images/dino/background.jpg'); /// File path: assets/images/dino/ball.png AssetGenImage get ball => const AssetGenImage('assets/images/dino/ball.png'); @@ -100,9 +100,9 @@ class $AssetsImagesSparkyGen { AssetGenImage get animation => const AssetGenImage('assets/images/sparky/animation.png'); - /// File path: assets/images/sparky/background.png + /// File path: assets/images/sparky/background.jpg AssetGenImage get background => - const AssetGenImage('assets/images/sparky/background.png'); + const AssetGenImage('assets/images/sparky/background.jpg'); /// File path: assets/images/sparky/ball.png AssetGenImage get ball => diff --git a/packages/pinball_ui/assets/images/button/dpad_down.png b/packages/pinball_ui/assets/images/button/dpad_down.png index 11bbb26f..71c4b0a7 100644 Binary files a/packages/pinball_ui/assets/images/button/dpad_down.png and b/packages/pinball_ui/assets/images/button/dpad_down.png differ diff --git a/packages/pinball_ui/assets/images/button/dpad_left.png b/packages/pinball_ui/assets/images/button/dpad_left.png index 943cacc4..a0979677 100644 Binary files a/packages/pinball_ui/assets/images/button/dpad_left.png and b/packages/pinball_ui/assets/images/button/dpad_left.png differ diff --git a/packages/pinball_ui/assets/images/button/dpad_right.png b/packages/pinball_ui/assets/images/button/dpad_right.png index 724b9f3e..9e7a2be9 100644 Binary files a/packages/pinball_ui/assets/images/button/dpad_right.png and b/packages/pinball_ui/assets/images/button/dpad_right.png differ diff --git a/packages/pinball_ui/assets/images/button/dpad_up.png b/packages/pinball_ui/assets/images/button/dpad_up.png index d1175d57..6113e3de 100644 Binary files a/packages/pinball_ui/assets/images/button/dpad_up.png and b/packages/pinball_ui/assets/images/button/dpad_up.png differ diff --git a/packages/pinball_ui/assets/images/button/pinball_button.png b/packages/pinball_ui/assets/images/button/pinball_button.png index 62373b85..8d10ec7c 100644 Binary files a/packages/pinball_ui/assets/images/button/pinball_button.png and b/packages/pinball_ui/assets/images/button/pinball_button.png differ diff --git a/packages/pinball_ui/assets/images/dialog/background.png b/packages/pinball_ui/assets/images/dialog/background.png index 0aad300f..82b3cb58 100644 Binary files a/packages/pinball_ui/assets/images/dialog/background.png and b/packages/pinball_ui/assets/images/dialog/background.png differ diff --git a/packages/pinball_ui/lib/src/theme/pinball_colors.dart b/packages/pinball_ui/lib/src/theme/pinball_colors.dart index eb920129..250db02c 100644 --- a/packages/pinball_ui/lib/src/theme/pinball_colors.dart +++ b/packages/pinball_ui/lib/src/theme/pinball_colors.dart @@ -5,6 +5,9 @@ abstract class PinballColors { /// Color: 0xFFFFFFFF static const Color white = Color(0xFFFFFFFF); + /// Color: 0xFF000000 + static const Color black = Color(0xFF000000); + /// Color: 0xFF0C32A4 static const Color darkBlue = Color(0xFF0C32A4); diff --git a/packages/pinball_ui/test/src/theme/pinball_colors_test.dart b/packages/pinball_ui/test/src/theme/pinball_colors_test.dart index 469ab142..ddc95eda 100644 --- a/packages/pinball_ui/test/src/theme/pinball_colors_test.dart +++ b/packages/pinball_ui/test/src/theme/pinball_colors_test.dart @@ -8,6 +8,10 @@ void main() { expect(PinballColors.white, const Color(0xFFFFFFFF)); }); + test('black is 0xFF000000', () { + expect(PinballColors.black, const Color(0xFF000000)); + }); + test('darkBlue is 0xFF0C32A4', () { expect(PinballColors.darkBlue, const Color(0xFF0C32A4)); }); diff --git a/packages/share_repository/lib/src/share_repository.dart b/packages/share_repository/lib/src/share_repository.dart index e01f5746..a3f0f942 100644 --- a/packages/share_repository/lib/src/share_repository.dart +++ b/packages/share_repository/lib/src/share_repository.dart @@ -12,11 +12,14 @@ class ShareRepository { final String _appUrl; /// Url to the Github Open Source Pinball project. - static const openSourceCode = 'https://github.com/VGVentures/pinball'; + static const openSourceCode = 'https://github.com/flutter/pinball'; /// Url to the Google IO Event. static const googleIOEvent = 'https://events.google.com/io/'; + /// Url to the Pinball game. + static const pinballGameUrl = 'https://pinball.flutter.dev'; + /// Returns a url to share the [value] on the given [platform]. /// /// The returned url can be opened using the [url_launcher](https://pub.dev/packages/url_launcher) package. diff --git a/pubspec.yaml b/pubspec.yaml index 1b71a25f..7f925688 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,7 +64,6 @@ flutter: - assets/images/components/ - assets/images/bonus_animation/ - assets/images/score/ - - assets/images/link_box/ - assets/images/loading_game/ flutter_gen: diff --git a/test/app/view/app_test.dart b/test/app/view/app_test.dart index 4612641d..b9f2ef16 100644 --- a/test/app/view/app_test.dart +++ b/test/app/view/app_test.dart @@ -5,6 +5,8 @@ import 'package:mocktail/mocktail.dart'; import 'package:pinball/app/app.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_audio/pinball_audio.dart'; +import 'package:platform_helper/platform_helper.dart'; +import 'package:share_repository/share_repository.dart'; class _MockAuthenticationRepository extends Mock implements AuthenticationRepository {} @@ -14,16 +16,27 @@ class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { } +class _MockShareRepository extends Mock implements ShareRepository {} + +class _MockPlatformHelper extends Mock implements PlatformHelper { + @override + bool get isMobile => false; +} + void main() { group('App', () { late AuthenticationRepository authenticationRepository; late LeaderboardRepository leaderboardRepository; + late ShareRepository shareRepository; late PinballAudioPlayer pinballAudioPlayer; + late PlatformHelper platformHelper; setUp(() { authenticationRepository = _MockAuthenticationRepository(); leaderboardRepository = _MockLeaderboardRepository(); + shareRepository = _MockShareRepository(); pinballAudioPlayer = _MockPinballAudioPlayer(); + platformHelper = _MockPlatformHelper(); when(pinballAudioPlayer.load).thenAnswer((_) => [Future.value()]); }); @@ -32,10 +45,12 @@ void main() { App( authenticationRepository: authenticationRepository, leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, pinballAudioPlayer: pinballAudioPlayer, + platformHelper: platformHelper, ), ); - await tester.pump(const Duration(milliseconds: 400)); + await tester.pump(const Duration(milliseconds: 1100)); expect(find.byType(PinballGamePage), findsOneWidget); }); }); diff --git a/test/game/behaviors/ball_theming_behavior_test.dart b/test/game/behaviors/ball_theming_behavior_test.dart deleted file mode 100644 index a59f3760..00000000 --- a/test/game/behaviors/ball_theming_behavior_test.dart +++ /dev/null @@ -1,93 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:bloc_test/bloc_test.dart'; -import 'package:flame/components.dart'; -import 'package:flame_bloc/flame_bloc.dart'; -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:pinball/game/behaviors/behaviors.dart'; -import 'package:pinball/select_character/select_character.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart' as theme; - -class _TestGame extends Forge2DGame { - @override - Future onLoad() async { - images.prefix = ''; - await images.loadAll([ - theme.Assets.images.dash.ball.keyName, - theme.Assets.images.dino.ball.keyName, - ]); - } - - Future pump( - List children, { - CharacterThemeCubit? characterThemeBloc, - }) async { - await ensureAdd( - FlameBlocProvider.value( - value: characterThemeBloc ?? CharacterThemeCubit(), - children: children, - ), - ); - } -} - -class _MockBallCubit extends Mock implements BallCubit {} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group( - 'BallThemingBehavior', - () { - final flameTester = FlameTester(_TestGame.new); - - test('can be instantiated', () { - expect( - BallThemingBehavior(), - isA(), - ); - }); - - flameTester.test( - 'loads', - (game) async { - final behavior = BallThemingBehavior(); - await game.pump([behavior]); - expect(game.descendants(), contains(behavior)); - }, - ); - - flameTester.test( - 'onNewState calls onThemeChanged on the ball bloc', - (game) async { - final ballBloc = _MockBallCubit(); - whenListen( - ballBloc, - const Stream.empty(), - initialState: const BallState.initial(), - ); - final ball = Ball.test(bloc: ballBloc); - final behavior = BallThemingBehavior(); - await game.pump([ - ball, - behavior, - ZCanvasComponent(), - Plunger.test(), - ]); - - const dinoThemeState = CharacterThemeState(theme.DinoTheme()); - behavior.onNewState(dinoThemeState); - await game.ready(); - - verify(() => ballBloc.onThemeChanged(dinoThemeState.characterTheme)) - .called(1); - }, - ); - }, - ); -} diff --git a/test/game/behaviors/bumper_noise_behavior_test.dart b/test/game/behaviors/bumper_noise_behavior_test.dart index 636da6ad..cf6c7900 100644 --- a/test/game/behaviors/bumper_noise_behavior_test.dart +++ b/test/game/behaviors/bumper_noise_behavior_test.dart @@ -16,9 +16,7 @@ class _TestGame extends Forge2DGame { return ensureAdd( FlameProvider.value( audioPlayer, - children: [ - child, - ], + children: [child], ), ); } @@ -36,26 +34,26 @@ class _MockContact extends Mock implements Contact {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('BumperNoiseBehavior', () {}); - - late PinballAudioPlayer audioPlayer; - final flameTester = FlameTester(_TestGame.new); - - setUp(() { - audioPlayer = _MockPinballAudioPlayer(); + group('BumperNoiseBehavior', () { + late PinballAudioPlayer audioPlayer; + final flameTester = FlameTester(_TestGame.new); + + setUp(() { + audioPlayer = _MockPinballAudioPlayer(); + }); + + flameTester.testGameWidget( + 'plays bumper sound', + setUp: (game, _) async { + final behavior = BumperNoiseBehavior(); + final parent = _TestBodyComponent(); + await game.pump(parent, audioPlayer: audioPlayer); + await parent.ensureAdd(behavior); + behavior.beginContact(Object(), _MockContact()); + }, + verify: (_, __) async { + verify(() => audioPlayer.play(PinballAudio.bumper)).called(1); + }, + ); }); - - flameTester.testGameWidget( - 'plays bumper sound', - setUp: (game, _) async { - final behavior = BumperNoiseBehavior(); - final parent = _TestBodyComponent(); - await game.pump(parent, audioPlayer: audioPlayer); - await parent.ensureAdd(behavior); - behavior.beginContact(Object(), _MockContact()); - }, - verify: (_, __) async { - verify(() => audioPlayer.play(PinballAudio.bumper)).called(1); - }, - ); } diff --git a/test/game/behaviors/character_selection_behavior_test.dart b/test/game/behaviors/character_selection_behavior_test.dart new file mode 100644 index 00000000..7fe5439f --- /dev/null +++ b/test/game/behaviors/character_selection_behavior_test.dart @@ -0,0 +1,185 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/select_character/select_character.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:pinball_theme/pinball_theme.dart' as theme; +import 'package:platform_helper/platform_helper.dart'; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + theme.Assets.images.dash.ball.keyName, + theme.Assets.images.dino.ball.keyName, + theme.Assets.images.dash.background.keyName, + theme.Assets.images.dino.background.keyName, + ]); + } + + Future pump( + List children, { + CharacterThemeCubit? characterThemeBloc, + PlatformHelper? platformHelper, + }) async { + await ensureAdd( + FlameBlocProvider.value( + value: characterThemeBloc ?? CharacterThemeCubit(), + children: [ + FlameProvider.value( + platformHelper ?? _MockPlatformHelper(), + children: children, + ), + ], + ), + ); + } +} + +class _MockBallCubit extends Mock implements BallCubit {} + +class _MockArcadeBackgroundCubit extends Mock implements ArcadeBackgroundCubit { +} + +class _MockPlatformHelper extends Mock implements PlatformHelper {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group( + 'CharacterSelectionBehavior', + () { + final flameTester = FlameTester(_TestGame.new); + + test('can be instantiated', () { + expect( + CharacterSelectionBehavior(), + isA(), + ); + }); + + flameTester.test( + 'loads', + (game) async { + final behavior = CharacterSelectionBehavior(); + await game.pump([behavior]); + expect(game.descendants(), contains(behavior)); + }, + ); + + flameTester.test( + 'onNewState does not call onCharacterSelected on the arcade background ' + 'bloc when platform is mobile', + (game) async { + final platformHelper = _MockPlatformHelper(); + when(() => platformHelper.isMobile).thenAnswer((_) => true); + final arcadeBackgroundBloc = _MockArcadeBackgroundCubit(); + whenListen( + arcadeBackgroundBloc, + const Stream.empty(), + initialState: const ArcadeBackgroundState.initial(), + ); + final behavior = CharacterSelectionBehavior(); + await game.pump( + [ + behavior, + ZCanvasComponent(), + Plunger.test(), + Ball.test(), + ], + platformHelper: platformHelper, + ); + + const dinoThemeState = CharacterThemeState(theme.DinoTheme()); + behavior.onNewState(dinoThemeState); + await game.ready(); + + verifyNever( + () => arcadeBackgroundBloc + .onCharacterSelected(dinoThemeState.characterTheme), + ); + }, + ); + + flameTester.test( + 'onNewState calls onCharacterSelected on the arcade background ' + 'bloc when platform is not mobile', + (game) async { + final platformHelper = _MockPlatformHelper(); + when(() => platformHelper.isMobile).thenAnswer((_) => false); + final arcadeBackgroundBloc = _MockArcadeBackgroundCubit(); + whenListen( + arcadeBackgroundBloc, + const Stream.empty(), + initialState: const ArcadeBackgroundState.initial(), + ); + final arcadeBackground = + ArcadeBackground.test(bloc: arcadeBackgroundBloc); + final behavior = CharacterSelectionBehavior(); + await game.pump( + [ + arcadeBackground, + behavior, + ZCanvasComponent(), + Plunger.test(), + Ball.test(), + ], + platformHelper: platformHelper, + ); + + const dinoThemeState = CharacterThemeState(theme.DinoTheme()); + behavior.onNewState(dinoThemeState); + await game.ready(); + + verify( + () => arcadeBackgroundBloc + .onCharacterSelected(dinoThemeState.characterTheme), + ).called(1); + }, + ); + + flameTester.test( + 'onNewState calls onCharacterSelected on the ball bloc', + (game) async { + final platformHelper = _MockPlatformHelper(); + when(() => platformHelper.isMobile).thenAnswer((_) => false); + final ballBloc = _MockBallCubit(); + whenListen( + ballBloc, + const Stream.empty(), + initialState: const BallState.initial(), + ); + final ball = Ball.test(bloc: ballBloc); + final behavior = CharacterSelectionBehavior(); + await game.pump( + [ + ball, + behavior, + ZCanvasComponent(), + Plunger.test(), + ArcadeBackground.test(), + ], + platformHelper: platformHelper, + ); + + const dinoThemeState = CharacterThemeState(theme.DinoTheme()); + behavior.onNewState(dinoThemeState); + await game.ready(); + + verify( + () => ballBloc.onCharacterSelected(dinoThemeState.characterTheme), + ).called(1); + }, + ); + }, + ); +} diff --git a/test/game/behaviors/cow_bumper_noise_behavior_test.dart b/test/game/behaviors/cow_bumper_noise_behavior_test.dart new file mode 100644 index 00000000..27a62e0b --- /dev/null +++ b/test/game/behaviors/cow_bumper_noise_behavior_test.dart @@ -0,0 +1,58 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class _TestGame extends Forge2DGame { + Future pump( + _TestBodyComponent child, { + required PinballAudioPlayer audioPlayer, + }) { + return ensureAdd( + FlameProvider.value( + audioPlayer, + children: [child], + ), + ); + } +} + +class _TestBodyComponent extends BodyComponent { + @override + Body createBody() => world.createBody(BodyDef()); +} + +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} + +class _MockContact extends Mock implements Contact {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('CowBumperNoiseBehavior', () { + late PinballAudioPlayer audioPlayer; + final flameTester = FlameTester(_TestGame.new); + + setUp(() { + audioPlayer = _MockPinballAudioPlayer(); + }); + flameTester.testGameWidget( + 'plays cow moo sound on contact', + setUp: (game, _) async { + final behavior = CowBumperNoiseBehavior(); + final parent = _TestBodyComponent(); + await game.pump(parent, audioPlayer: audioPlayer); + await parent.ensureAdd(behavior); + behavior.beginContact(Object(), _MockContact()); + }, + verify: (_, __) async { + verify(() => audioPlayer.play(PinballAudio.cowMoo)).called(1); + }, + ); + }); +} diff --git a/test/game/behaviors/kicker_noise_behavior_test.dart b/test/game/behaviors/kicker_noise_behavior_test.dart new file mode 100644 index 00000000..4db18ab4 --- /dev/null +++ b/test/game/behaviors/kicker_noise_behavior_test.dart @@ -0,0 +1,59 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class _TestGame extends Forge2DGame { + Future pump( + _TestBodyComponent child, { + required PinballAudioPlayer audioPlayer, + }) { + return ensureAdd( + FlameProvider.value( + audioPlayer, + children: [child], + ), + ); + } +} + +class _TestBodyComponent extends BodyComponent { + @override + Body createBody() => world.createBody(BodyDef()); +} + +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} + +class _MockContact extends Mock implements Contact {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('KickerNoiseBehavior', () { + late PinballAudioPlayer audioPlayer; + final flameTester = FlameTester(_TestGame.new); + + setUp(() { + audioPlayer = _MockPinballAudioPlayer(); + }); + + flameTester.testGameWidget( + 'plays kicker sound', + setUp: (game, _) async { + final behavior = KickerNoiseBehavior(); + final parent = _TestBodyComponent(); + await game.pump(parent, audioPlayer: audioPlayer); + await parent.ensureAdd(behavior); + behavior.beginContact(Object(), _MockContact()); + }, + verify: (_, __) async { + verify(() => audioPlayer.play(PinballAudio.kicker)).called(1); + }, + ); + }); +} diff --git a/test/game/bloc/game_bloc_test.dart b/test/game/bloc/game_bloc_test.dart index 9e8c6354..2108b950 100644 --- a/test/game/bloc/game_bloc_test.dart +++ b/test/game/bloc/game_bloc_test.dart @@ -91,6 +91,28 @@ void main() { ], ); + blocTest( + "multiplier doesn't increase score above the max score", + build: GameBloc.new, + seed: () => const GameState( + totalScore: 9999999998, + roundScore: 1, + multiplier: 2, + rounds: 1, + bonusHistory: [], + status: GameStatus.playing, + ), + act: (bloc) => bloc.add(const RoundLost()), + expect: () => [ + isA() + ..having( + (state) => state.totalScore, + 'totalScore', + 9999999999, + ) + ], + ); + blocTest( 'resets multiplier when round is lost', build: GameBloc.new, @@ -167,6 +189,23 @@ void main() { ), ], ); + + blocTest( + "doesn't increase score above the max score", + build: GameBloc.new, + seed: () => const GameState( + totalScore: 9999999998, + roundScore: 0, + multiplier: 1, + rounds: 1, + bonusHistory: [], + status: GameStatus.playing, + ), + act: (bloc) => bloc.add(const Scored(points: 2)), + expect: () => [ + isA()..having((state) => state.roundScore, 'roundScore', 1) + ], + ); }); group('MultiplierIncreased', () { diff --git a/test/game/components/android_acres/android_acres_test.dart b/test/game/components/android_acres/android_acres_test.dart index 5bf22da9..14d3d69e 100644 --- a/test/game/components/android_acres/android_acres_test.dart +++ b/test/game/components/android_acres/android_acres_test.dart @@ -4,7 +4,7 @@ import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball/game/behaviors/bumper_noise_behavior.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -62,7 +62,7 @@ void main() { group('loads', () { flameTester.test( - 'an AndroidSpaceship', + 'an AndroidSpaceship', (game) async { await game.pump(AndroidAcres()); expect( @@ -124,11 +124,26 @@ void main() { for (final bumper in bumpers) { expect( bumper.firstChild(), - isNotNull, + isA(), ); } }, ); + + flameTester.test( + 'one AndroidBumper with CowBumperNoiseBehavior', + (game) async { + await game.pump(AndroidAcres()); + final bumpers = game.descendants().whereType(); + + expect( + bumpers.singleWhere( + (bumper) => bumper.firstChild() != null, + ), + isA(), + ); + }, + ); }); flameTester.test('adds a FlameBlocProvider', (game) async { diff --git a/test/game/components/backbox/backbox_test.dart b/test/game/components/backbox/backbox_test.dart index 65474cd2..26f413f7 100644 --- a/test/game/components/backbox/backbox_test.dart +++ b/test/game/components/backbox/backbox_test.dart @@ -20,7 +20,10 @@ import 'package:pinball/l10n/l10n.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; +import 'package:pinball_ui/pinball_ui.dart'; import 'package:platform_helper/platform_helper.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:share_repository/share_repository.dart'; class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents, HasTappables { @@ -36,17 +39,34 @@ class _TestGame extends Forge2DGame character.leaderboardIcon.keyName, Assets.images.backbox.marquee.keyName, Assets.images.backbox.displayDivider.keyName, + Assets.images.backbox.button.facebook.keyName, + Assets.images.backbox.button.twitter.keyName, Assets.images.backbox.displayTitleDecoration.keyName, + Assets.images.displayArrows.arrowLeft.keyName, + Assets.images.displayArrows.arrowRight.keyName, ]); } - Future pump(Backbox component) { - return ensureAdd( + Future pump( + Backbox component, { + PlatformHelper? platformHelper, + }) async { + // Not needed once https://github.com/flame-engine/flame/issues/1607 + // is fixed + await onLoad(); + await ensureAdd( FlameBlocProvider.value( value: GameBloc(), children: [ - FlameProvider.value( - _MockAppLocalizations(), + MultiFlameProvider( + providers: [ + FlameProvider.value( + _MockAppLocalizations(), + ), + FlameProvider.value( + platformHelper ?? _MockPlatformHelper(), + ), + ], children: [component], ), ], @@ -75,8 +95,16 @@ class _MockBackboxBloc extends Mock implements BackboxBloc {} class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { } +class _MockShareRepository extends Mock implements ShareRepository {} + class _MockTapDownInfo extends Mock implements TapDownInfo {} +class _MockTapUpInfo extends Mock implements TapUpInfo {} + +class _MockUrlLauncher extends Mock + with MockPlatformInterfaceMixin + implements UrlLauncherPlatform {} + class _MockAppLocalizations extends Mock implements AppLocalizations { @override String get score => ''; @@ -105,6 +133,15 @@ class _MockAppLocalizations extends Mock implements AppLocalizations { @override String get loading => ''; + @override + String get letEveryone => ''; + + @override + String get bySharingYourScore => ''; + + @override + String get socialMediaAccount => ''; + @override String get shareYourScore => ''; @@ -134,6 +171,9 @@ class _MockAppLocalizations extends Mock implements AppLocalizations { @override String get leaderboardErrorMessage => ''; + + @override + String iGotScoreAtPinball(String _) => ''; } void main() { @@ -143,6 +183,7 @@ void main() { late BackboxBloc bloc; late PlatformHelper platformHelper; + late UrlLauncherPlatform urlLauncher; setUp(() { bloc = _MockBackboxBloc(); @@ -161,9 +202,12 @@ void main() { (game) async { final backbox = Backbox.test( bloc: bloc, + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, platformHelper: platformHelper, ); - await game.pump(backbox); expect(game.descendants(), contains(backbox)); }, ); @@ -178,8 +222,9 @@ void main() { await game.pump( Backbox.test( bloc: bloc, - platformHelper: platformHelper, + shareRepository: _MockShareRepository(), ), + platformHelper: platformHelper, ); await tester.pump(); }, @@ -199,9 +244,12 @@ void main() { leaderboardRepository: _MockLeaderboardRepository(), initialEntries: [LeaderboardEntryData.empty], ), + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, platformHelper: platformHelper, ); - await game.pump(backbox); backbox.requestInitials( score: 0, character: game.character, @@ -230,9 +278,12 @@ void main() { ); final backbox = Backbox.test( bloc: bloc, + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, platformHelper: platformHelper, ); - await game.pump(backbox); game.onKeyEvent(_mockKeyUp(LogicalKeyboardKey.enter), {}); verify( @@ -258,9 +309,12 @@ void main() { ); final backbox = Backbox.test( bloc: bloc, + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, platformHelper: platformHelper, ); - await game.pump(backbox); expect( game.descendants().whereType().length, @@ -270,7 +324,8 @@ void main() { ); flameTester.test( - 'adds the mobile controls overlay when platform is mobile', + 'adds the mobile controls overlay ' + 'when platform is mobile at InitialsFormState', (game) async { final bloc = _MockBackboxBloc(); final platformHelper = _MockPlatformHelper(); @@ -286,9 +341,12 @@ void main() { when(() => platformHelper.isMobile).thenReturn(true); final backbox = Backbox.test( bloc: bloc, + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, platformHelper: platformHelper, ); - await game.pump(backbox); expect( game.overlays.value, @@ -297,6 +355,35 @@ void main() { }, ); + flameTester.test( + 'remove the mobile controls overlay ' + 'when InitialsSuccessState', + (game) async { + final bloc = _MockBackboxBloc(); + final platformHelper = _MockPlatformHelper(); + final state = InitialsSuccessState(score: 10); + whenListen( + bloc, + Stream.empty(), + initialState: state, + ); + when(() => platformHelper.isMobile).thenReturn(true); + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, + platformHelper: platformHelper, + ); + + expect( + game.overlays.value, + isNot(contains(PinballGame.mobileControlsOverlay)), + ); + }, + ); + flameTester.test( 'adds InitialsSubmissionSuccessDisplay on InitialsSuccessState', (game) async { @@ -308,9 +395,12 @@ void main() { ); final backbox = Backbox.test( bloc: bloc, + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, platformHelper: platformHelper, ); - await game.pump(backbox); expect( game.descendants().whereType().length, @@ -330,9 +420,12 @@ void main() { ); final backbox = Backbox.test( bloc: bloc, + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, platformHelper: platformHelper, ); - await game.pump(backbox); final shareLink = game.descendants().whereType().first; @@ -359,9 +452,12 @@ void main() { ); final backbox = Backbox.test( bloc: bloc, + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, platformHelper: platformHelper, ); - await game.pump(backbox); expect( game @@ -373,6 +469,151 @@ void main() { }, ); + group('ShareDisplay', () { + setUp(() async { + urlLauncher = _MockUrlLauncher(); + UrlLauncherPlatform.instance = urlLauncher; + }); + + flameTester.test( + 'adds ShareDisplay on ShareState', + (game) async { + final state = ShareState(score: 100); + whenListen( + bloc, + const Stream.empty(), + initialState: state, + ); + final backbox = Backbox.test( + bloc: bloc, + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, + platformHelper: platformHelper, + ); + + expect( + game.descendants().whereType().length, + equals(1), + ); + }, + ); + + flameTester.test( + 'opens Facebook link when sharing with Facebook', + (game) async { + when(() => urlLauncher.canLaunch(any())) + .thenAnswer((_) async => true); + when( + () => urlLauncher.launch( + any(), + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ).thenAnswer((_) async => true); + + final state = ShareState(score: 100); + whenListen( + bloc, + const Stream.empty(), + initialState: state, + ); + + final shareRepository = _MockShareRepository(); + const fakeUrl = 'http://fakeUrl'; + when( + () => shareRepository.shareText( + value: any(named: 'value'), + platform: SharePlatform.facebook, + ), + ).thenReturn(fakeUrl); + + final backbox = Backbox.test( + bloc: bloc, + shareRepository: shareRepository, + ); + await game.pump( + backbox, + platformHelper: platformHelper, + ); + + final facebookButton = + game.descendants().whereType().first; + facebookButton.onTapUp(_MockTapUpInfo()); + + await game.ready(); + + verify( + () => shareRepository.shareText( + value: any(named: 'value'), + platform: SharePlatform.facebook, + ), + ).called(1); + }, + ); + + flameTester.test( + 'opens Twitter link when sharing with Twitter', + (game) async { + final state = ShareState(score: 100); + whenListen( + bloc, + Stream.value(state), + initialState: state, + ); + + final shareRepository = _MockShareRepository(); + const fakeUrl = 'http://fakeUrl'; + when( + () => shareRepository.shareText( + value: any(named: 'value'), + platform: SharePlatform.twitter, + ), + ).thenReturn(fakeUrl); + when(() => urlLauncher.canLaunch(any())) + .thenAnswer((_) async => true); + when( + () => urlLauncher.launch( + any(), + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ).thenAnswer((_) async => true); + + final backbox = Backbox.test( + bloc: bloc, + shareRepository: shareRepository, + ); + await game.pump( + backbox, + platformHelper: platformHelper, + ); + + final facebookButton = + game.descendants().whereType().first; + facebookButton.onTapUp(_MockTapUpInfo()); + + await game.ready(); + + verify( + () => shareRepository.shareText( + value: any(named: 'value'), + platform: SharePlatform.twitter, + ), + ).called(1); + }, + ); + }); + flameTester.test( 'adds LeaderboardDisplay on LeaderboardSuccessState', (game) async { @@ -384,9 +625,12 @@ void main() { final backbox = Backbox.test( bloc: bloc, + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, platformHelper: platformHelper, ); - await game.pump(backbox); expect( game.descendants().whereType().length, @@ -406,9 +650,12 @@ void main() { final backbox = Backbox.test( bloc: bloc, + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, platformHelper: platformHelper, ); - await game.pump(backbox); expect( game.descendants().whereType().length, @@ -429,9 +676,12 @@ void main() { final backbox = Backbox.test( bloc: bloc, + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, platformHelper: platformHelper, ); - await game.pump(backbox); backbox.removeFromParent(); await game.ready(); @@ -469,9 +719,12 @@ void main() { final backbox = Backbox.test( bloc: bloc, + shareRepository: _MockShareRepository(), + ); + await game.pump( + backbox, platformHelper: platformHelper, ); - await game.pump(backbox); game.update(4); verify( diff --git a/test/game/components/backbox/bloc/backbox_bloc_test.dart b/test/game/components/backbox/bloc/backbox_bloc_test.dart index aec46186..050307dc 100644 --- a/test/game/components/backbox/bloc/backbox_bloc_test.dart +++ b/test/game/components/backbox/bloc/backbox_bloc_test.dart @@ -137,6 +137,25 @@ void main() { ); }); + group('ShareScoreRequested', () { + blocTest( + 'emits ShareState', + setUp: () { + leaderboardRepository = _MockLeaderboardRepository(); + }, + build: () => BackboxBloc( + leaderboardRepository: leaderboardRepository, + initialEntries: emptyEntries, + ), + act: (bloc) => bloc.add( + ShareScoreRequested(score: 100), + ), + expect: () => [ + ShareState(score: 100), + ], + ); + }); + group('LeaderboardRequested', () { blocTest( 'adds [LoadingState, LeaderboardSuccessState] when request succeeds', diff --git a/test/game/components/backbox/bloc/backbox_state_test.dart b/test/game/components/backbox/bloc/backbox_state_test.dart index c41562b0..ba6d1c08 100644 --- a/test/game/components/backbox/bloc/backbox_state_test.dart +++ b/test/game/components/backbox/bloc/backbox_state_test.dart @@ -203,5 +203,23 @@ 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), + ), + ); + }); + }); }); } diff --git a/test/game/components/backbox/displays/game_over_info_display_test.dart b/test/game/components/backbox/displays/game_over_info_display_test.dart index 6b79d9fd..2bee4005 100644 --- a/test/game/components/backbox/displays/game_over_info_display_test.dart +++ b/test/game/components/backbox/displays/game_over_info_display_test.dart @@ -68,6 +68,8 @@ class _MockAppLocalizations extends Mock implements AppLocalizations { class _MockTapDownInfo extends Mock implements TapDownInfo {} +class _MockTapUpInfo extends Mock implements TapUpInfo {} + class _MockUrlLauncher extends Mock with MockPlatformInterfaceMixin implements UrlLauncherPlatform {} @@ -135,7 +137,7 @@ void main() { final googleLink = component.descendants().whereType().first; - googleLink.onTapDown(_MockTapDownInfo()); + googleLink.onTapUp(_MockTapUpInfo()); await game.ready(); diff --git a/test/game/components/backbox/displays/leaderboard_display_test.dart b/test/game/components/backbox/displays/leaderboard_display_test.dart index 263222fc..46fe6cdc 100644 --- a/test/game/components/backbox/displays/leaderboard_display_test.dart +++ b/test/game/components/backbox/displays/leaderboard_display_test.dart @@ -1,6 +1,7 @@ // ignore_for_file: cascade_invocations import 'package:flame/components.dart'; +import 'package:flame/game.dart'; import 'package:flame_forge2d/forge2d_game.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -8,8 +9,9 @@ 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_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; +import 'package:pinball_theme/pinball_theme.dart' hide Assets; class _MockAppLocalizations extends Mock implements AppLocalizations { @override @@ -22,12 +24,16 @@ class _MockAppLocalizations extends Mock implements AppLocalizations { String get name => 'name'; } -class _TestGame extends Forge2DGame { +class _TestGame extends Forge2DGame with HasTappables { @override Future onLoad() async { await super.onLoad(); images.prefix = ''; - await images.load(const AndroidTheme().leaderboardIcon.keyName); + await images.loadAll([ + const AndroidTheme().leaderboardIcon.keyName, + Assets.images.displayArrows.arrowLeft.keyName, + Assets.images.displayArrows.arrowRight.keyName, + ]); } Future pump(LeaderboardDisplay component) { @@ -40,6 +46,59 @@ class _TestGame extends Forge2DGame { } } +const leaderboard = [ + 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, + ), + LeaderboardEntryData( + playerInitials: 'EEE', + score: 123467, + character: CharacterType.android, + ), + LeaderboardEntryData( + playerInitials: 'FFF', + score: 123468, + character: CharacterType.android, + ), + LeaderboardEntryData( + playerInitials: 'GGG', + score: 1234689, + character: CharacterType.android, + ), + LeaderboardEntryData( + playerInitials: 'HHH', + score: 12346891, + character: CharacterType.android, + ), + LeaderboardEntryData( + playerInitials: 'III', + score: 123468912, + character: CharacterType.android, + ), + LeaderboardEntryData( + playerInitials: 'JJJ', + score: 1234689121, + character: CharacterType.android, + ), +]; + void main() { group('LeaderboardDisplay', () { TestWidgetsFlutterBinding.ensureInitialized(); @@ -57,43 +116,20 @@ void main() { 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, - ), - ], - ), - ); + flameTester.test('renders the first 5 entries', (game) async { + await game.pump(LeaderboardDisplay(entries: leaderboard)); for (final text in [ 'AAA', 'BBB', 'CCC', 'DDD', + 'EEE', '1st', '2nd', '3rd', - '4th' + '4th', + '5th', ]) { expect( game @@ -105,5 +141,120 @@ void main() { ); } }); + + flameTester.test('can open the second page', (game) async { + final display = LeaderboardDisplay(entries: leaderboard); + await game.pump(display); + + final arrow = game + .descendants() + .whereType() + .where((arrow) => arrow.direction == ArrowIconDirection.right) + .single; + + // Tap the arrow + arrow.onTap(); + // Wait for the transition to finish + display.updateTree(5); + await game.ready(); + + for (final text in [ + 'FFF', + 'GGG', + 'HHH', + 'III', + 'JJJ', + '6th', + '7th', + '8th', + '9th', + '10th', + ]) { + expect( + game + .descendants() + .whereType() + .where((textComponent) => textComponent.text == text) + .length, + equals(1), + ); + } + }); + + flameTester.test( + 'can open the second page and go back to the first', + (game) async { + final display = LeaderboardDisplay(entries: leaderboard); + await game.pump(display); + + var arrow = game + .descendants() + .whereType() + .where((arrow) => arrow.direction == ArrowIconDirection.right) + .single; + + // Tap the arrow + arrow.onTap(); + // Wait for the transition to finish + display.updateTree(5); + await game.ready(); + + for (final text in [ + 'FFF', + 'GGG', + 'HHH', + 'III', + 'JJJ', + '6th', + '7th', + '8th', + '9th', + '10th', + ]) { + expect( + game + .descendants() + .whereType() + .where((textComponent) => textComponent.text == text) + .length, + equals(1), + ); + } + + arrow = game + .descendants() + .whereType() + .where((arrow) => arrow.direction == ArrowIconDirection.left) + .single; + + // Tap the arrow + arrow.onTap(); + // Wait for the transition to finish + display.updateTree(5); + await game.ready(); + + for (final text in [ + 'AAA', + 'BBB', + 'CCC', + 'DDD', + 'EEE', + '1st', + '2nd', + '3rd', + '4th', + '5th', + ]) { + expect( + game + .descendants() + .whereType() + .where((textComponent) => textComponent.text == text) + .length, + equals(1), + ); + } + }, + ); }); } diff --git a/test/game/components/backbox/displays/share_display_test.dart b/test/game/components/backbox/displays/share_display_test.dart new file mode 100644 index 00000000..ddcb4001 --- /dev/null +++ b/test/game/components/backbox/displays/share_display_test.dart @@ -0,0 +1,112 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/game.dart'; +import 'package:flame/input.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/bloc/game_bloc.dart'; +import 'package:pinball/game/components/backbox/displays/share_display.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class _TestGame extends Forge2DGame with HasTappables { + @override + Future onLoad() async { + await super.onLoad(); + images.prefix = ''; + await images.loadAll( + [ + Assets.images.backbox.button.facebook.keyName, + Assets.images.backbox.button.twitter.keyName, + ], + ); + } + + Future pump(ShareDisplay component) { + return ensureAdd( + FlameBlocProvider.value( + value: GameBloc(), + children: [ + FlameProvider.value( + _MockAppLocalizations(), + children: [component], + ), + ], + ), + ); + } +} + +class _MockAppLocalizations extends Mock implements AppLocalizations { + @override + String get letEveryone => ''; + + @override + String get bySharingYourScore => ''; + + @override + String get socialMediaAccount => ''; +} + +class _MockTapUpInfo extends Mock implements TapUpInfo {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final flameTester = FlameTester(_TestGame.new); + + group('ShareDisplay', () { + flameTester.test( + 'loads correctly', + (game) async { + final component = ShareDisplay(); + await game.pump(component); + expect(game.descendants(), contains(component)); + }, + ); + + flameTester.test( + 'calls onShare when Facebook button is tapped', + (game) async { + var tapped = false; + + final tapUpInfo = _MockTapUpInfo(); + final component = ShareDisplay( + onShare: (_) => tapped = true, + ); + await game.pump(component); + + final facebookButton = + component.descendants().whereType().first; + + facebookButton.onTapUp(tapUpInfo); + + expect(tapped, isTrue); + }, + ); + + flameTester.test( + 'calls onShare when Twitter button is tapped', + (game) async { + var tapped = false; + + final tapUpInfo = _MockTapUpInfo(); + final component = ShareDisplay( + onShare: (_) => tapped = true, + ); + await game.pump(component); + + final twitterButton = + component.descendants().whereType().first; + + twitterButton.onTapUp(tapUpInfo); + + expect(tapped, isTrue); + }, + ); + }); +} diff --git a/test/game/components/game_bloc_status_listener_test.dart b/test/game/components/game_bloc_status_listener_test.dart index 33e40296..7d0a6c6b 100644 --- a/test/game/components/game_bloc_status_listener_test.dart +++ b/test/game/components/game_bloc_status_listener_test.dart @@ -1,6 +1,7 @@ // ignore_for_file: cascade_invocations import 'package:flame/components.dart'; +import 'package:flame/game.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; @@ -14,8 +15,10 @@ import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; +import 'package:platform_helper/platform_helper.dart'; +import 'package:share_repository/share_repository.dart'; -class _TestGame extends Forge2DGame { +class _TestGame extends Forge2DGame with HasTappables { @override Future onLoad() async { images.prefix = ''; @@ -24,6 +27,8 @@ class _TestGame extends Forge2DGame { const theme.DashTheme().leaderboardIcon.keyName, Assets.images.backbox.marquee.keyName, Assets.images.backbox.displayDivider.keyName, + Assets.images.displayArrows.arrowLeft.keyName, + Assets.images.displayArrows.arrowRight.keyName, ], ); } @@ -51,6 +56,9 @@ class _TestGame extends Forge2DGame { FlameProvider.value( _MockAppLocalizations(), ), + FlameProvider.value( + _MockPlatformHelper(), + ), ], children: children, ), @@ -65,6 +73,13 @@ class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { } +class _MockShareRepository extends Mock implements ShareRepository {} + +class _MockPlatformHelper extends Mock implements PlatformHelper { + @override + bool get isMobile => false; +} + class _MockAppLocalizations extends Mock implements AppLocalizations { @override String get score => ''; @@ -149,9 +164,11 @@ void main() { 'changes the backbox display', (game) async { final component = GameBlocStatusListener(); - final repository = _MockLeaderboardRepository(); + final leaderboardRepository = _MockLeaderboardRepository(); + final shareRepository = _MockShareRepository(); final backbox = Backbox( - leaderboardRepository: repository, + leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, entries: const [], ); @@ -165,9 +182,11 @@ void main() { 'removes FlipperKeyControllingBehavior from Flipper', (game) async { final component = GameBlocStatusListener(); - final repository = _MockLeaderboardRepository(); + final leaderboardRepository = _MockLeaderboardRepository(); + final shareRepository = _MockShareRepository(); final backbox = Backbox( - leaderboardRepository: repository, + leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, entries: const [], ); final flipper = Flipper.test(side: BoardSide.left); @@ -192,9 +211,11 @@ void main() { 'removes PlungerKeyControllingBehavior from Plunger', (game) async { final component = GameBlocStatusListener(); - final repository = _MockLeaderboardRepository(); + final leaderboardRepository = _MockLeaderboardRepository(); + final shareRepository = _MockShareRepository(); final backbox = Backbox( - leaderboardRepository: repository, + leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, entries: const [], ); final plunger = Plunger.test(); @@ -220,9 +241,11 @@ void main() { (game) async { final audioPlayer = _MockPinballAudioPlayer(); final component = GameBlocStatusListener(); - final repository = _MockLeaderboardRepository(); + final leaderboardRepository = _MockLeaderboardRepository(); + final shareRepository = _MockShareRepository(); final backbox = Backbox( - leaderboardRepository: repository, + leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, entries: const [], ); await game.pump( @@ -272,9 +295,11 @@ void main() { 'adds key controlling behavior to Flippers when the game is started', (game) async { final component = GameBlocStatusListener(); - final repository = _MockLeaderboardRepository(); + final leaderboardRepository = _MockLeaderboardRepository(); + final shareRepository = _MockShareRepository(); final backbox = Backbox( - leaderboardRepository: repository, + leaderboardRepository: leaderboardRepository, + shareRepository: shareRepository, entries: const [], ); final flipper = Flipper.test(side: BoardSide.left); diff --git a/test/game/components/golden/backbox.png b/test/game/components/golden/backbox.png index 962573ab..15426d4f 100644 Binary files a/test/game/components/golden/backbox.png and b/test/game/components/golden/backbox.png differ diff --git a/test/game/components/google_gallery/behaviors/google_word_bonus_behavior_test.dart b/test/game/components/google_gallery/behaviors/google_word_bonus_behavior_test.dart new file mode 100644 index 00000000..4b3ec2bd --- /dev/null +++ b/test/game/components/google_gallery/behaviors/google_word_bonus_behavior_test.dart @@ -0,0 +1,167 @@ +// ignore_for_file: cascade_invocations + +import 'dart:async'; + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/game/components/google_gallery/behaviors/behaviors.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.googleWord.letter1.lit.keyName, + Assets.images.googleWord.letter1.dimmed.keyName, + Assets.images.googleWord.letter2.lit.keyName, + Assets.images.googleWord.letter2.dimmed.keyName, + Assets.images.googleWord.letter3.lit.keyName, + Assets.images.googleWord.letter3.dimmed.keyName, + Assets.images.googleWord.letter4.lit.keyName, + Assets.images.googleWord.letter4.dimmed.keyName, + Assets.images.googleWord.letter5.lit.keyName, + Assets.images.googleWord.letter5.dimmed.keyName, + Assets.images.googleWord.letter6.lit.keyName, + Assets.images.googleWord.letter6.dimmed.keyName, + ]); + } + + Future pump( + GoogleGallery child, { + required GameBloc gameBloc, + required GoogleWordCubit googleWordBloc, + }) async { + // Not needed once https://github.com/flame-engine/flame/issues/1607 + // is fixed + await onLoad(); + await ensureAdd( + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value( + value: gameBloc, + ), + FlameBlocProvider.value( + value: googleWordBloc, + ), + ], + children: [child], + ), + ); + } +} + +class _MockGameBloc extends Mock implements GameBloc {} + +class _MockGoogleWordCubit extends Mock implements GoogleWordCubit {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('GoogleWordBonusBehavior', () { + late GameBloc gameBloc; + + setUp(() { + gameBloc = _MockGameBloc(); + }); + + final flameTester = FlameTester(_TestGame.new); + + flameTester.testGameWidget( + 'adds GameBonus.googleWord to the game when all letters ' + 'in google word are activated and calls onBonusAwarded', + setUp: (game, tester) async { + final behavior = GoogleWordBonusBehavior(); + final parent = GoogleGallery.test(); + final googleWord = GoogleWord(position: Vector2.zero()); + final googleWordBloc = _MockGoogleWordCubit(); + final streamController = StreamController(); + + whenListen( + googleWordBloc, + streamController.stream, + initialState: GoogleWordState.initial(), + ); + + await parent.add(googleWord); + await game.pump( + parent, + gameBloc: gameBloc, + googleWordBloc: googleWordBloc, + ); + await parent.ensureAdd(behavior); + + streamController.add( + const GoogleWordState( + letterSpriteStates: { + 0: GoogleLetterSpriteState.lit, + 1: GoogleLetterSpriteState.lit, + 2: GoogleLetterSpriteState.lit, + 3: GoogleLetterSpriteState.lit, + 4: GoogleLetterSpriteState.lit, + 5: GoogleLetterSpriteState.lit, + }, + ), + ); + await tester.pump(); + + verify( + () => gameBloc.add(const BonusActivated(GameBonus.googleWord)), + ).called(1); + verify(googleWordBloc.onBonusAwarded).called(1); + }, + ); + + flameTester.testGameWidget( + 'adds BonusBallSpawningBehavior to the game when all letters ' + 'in google word are activated', + setUp: (game, tester) async { + final behavior = GoogleWordBonusBehavior(); + final parent = GoogleGallery.test(); + final googleWord = GoogleWord(position: Vector2.zero()); + final googleWordBloc = _MockGoogleWordCubit(); + final streamController = StreamController(); + + whenListen( + googleWordBloc, + streamController.stream, + initialState: GoogleWordState.initial(), + ); + + await parent.add(googleWord); + await game.pump( + parent, + gameBloc: gameBloc, + googleWordBloc: googleWordBloc, + ); + await parent.ensureAdd(behavior); + + streamController.add( + const GoogleWordState( + letterSpriteStates: { + 0: GoogleLetterSpriteState.lit, + 1: GoogleLetterSpriteState.lit, + 2: GoogleLetterSpriteState.lit, + 3: GoogleLetterSpriteState.lit, + 4: GoogleLetterSpriteState.lit, + 5: GoogleLetterSpriteState.lit, + }, + ), + ); + await tester.pump(); + await game.ready(); + + expect( + game.descendants().whereType().length, + equals(1), + ); + }, + ); + }); +} diff --git a/test/game/components/google_gallery/google_gallery_test.dart b/test/game/components/google_gallery/google_gallery_test.dart new file mode 100644 index 00000000..9551285f --- /dev/null +++ b/test/game/components/google_gallery/google_gallery_test.dart @@ -0,0 +1,110 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/game/components/google_gallery/behaviors/behaviors.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.googleWord.letter1.lit.keyName, + Assets.images.googleWord.letter1.dimmed.keyName, + Assets.images.googleWord.letter2.lit.keyName, + Assets.images.googleWord.letter2.dimmed.keyName, + Assets.images.googleWord.letter3.lit.keyName, + Assets.images.googleWord.letter3.dimmed.keyName, + Assets.images.googleWord.letter4.lit.keyName, + Assets.images.googleWord.letter4.dimmed.keyName, + Assets.images.googleWord.letter5.lit.keyName, + Assets.images.googleWord.letter5.dimmed.keyName, + Assets.images.googleWord.letter6.lit.keyName, + Assets.images.googleWord.letter6.dimmed.keyName, + Assets.images.googleRollover.left.decal.keyName, + Assets.images.googleRollover.left.pin.keyName, + Assets.images.googleRollover.right.decal.keyName, + Assets.images.googleRollover.right.pin.keyName, + ]); + } + + Future pump(GoogleGallery child) async { + await ensureAdd( + FlameBlocProvider.value( + value: _MockGameBloc(), + children: [child], + ), + ); + } +} + +class _MockGameBloc extends Mock implements GameBloc {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final flameTester = FlameTester(_TestGame.new); + + group('GoogleGallery', () { + flameTester.test('loads correctly', (game) async { + final component = GoogleGallery(); + await game.pump(component); + expect(game.descendants(), contains(component)); + }); + + group('loads', () { + flameTester.test( + 'two GoogleRollovers', + (game) async { + await game.pump(GoogleGallery()); + expect( + game.descendants().whereType().length, + equals(2), + ); + }, + ); + + flameTester.test( + 'a GoogleWord', + (game) async { + await game.pump(GoogleGallery()); + expect( + game.descendants().whereType().length, + equals(1), + ); + }, + ); + }); + + group('adds', () { + flameTester.test( + 'ScoringContactBehavior to GoogleRollovers', + (game) async { + await game.pump(GoogleGallery()); + + game.descendants().whereType().forEach( + (rollover) => expect( + rollover.firstChild(), + isNotNull, + ), + ); + }, + ); + + flameTester.test('a GoogleWordBonusBehavior', (game) async { + final component = GoogleGallery(); + await game.pump(component); + expect( + component.descendants().whereType().single, + isNotNull, + ); + }); + }); + }); +} diff --git a/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart b/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart deleted file mode 100644 index e23c1fd2..00000000 --- a/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart +++ /dev/null @@ -1,94 +0,0 @@ -// ignore_for_file: cascade_invocations - -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/components/google_word/behaviors/behaviors.dart'; -import 'package:pinball/game/game.dart'; -import 'package:pinball_audio/pinball_audio.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -class _TestGame extends Forge2DGame { - @override - Future onLoad() async { - images.prefix = ''; - await images.loadAll([ - Assets.images.googleWord.letter1.lit.keyName, - Assets.images.googleWord.letter1.dimmed.keyName, - Assets.images.googleWord.letter2.lit.keyName, - Assets.images.googleWord.letter2.dimmed.keyName, - Assets.images.googleWord.letter3.lit.keyName, - Assets.images.googleWord.letter3.dimmed.keyName, - Assets.images.googleWord.letter4.lit.keyName, - Assets.images.googleWord.letter4.dimmed.keyName, - Assets.images.googleWord.letter5.lit.keyName, - Assets.images.googleWord.letter5.dimmed.keyName, - Assets.images.googleWord.letter6.lit.keyName, - Assets.images.googleWord.letter6.dimmed.keyName, - ]); - } - - Future pump(GoogleWord child, {required GameBloc gameBloc}) async { - await ensureAdd( - FlameBlocProvider.value( - value: gameBloc, - children: [ - FlameProvider.value( - _MockPinballAudioPlayer(), - children: [child], - ) - ], - ), - ); - } -} - -class _MockGameBloc extends Mock implements GameBloc {} - -class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group('GoogleWordBonusBehaviors', () { - late GameBloc gameBloc; - - setUp(() { - gameBloc = _MockGameBloc(); - }); - - final flameTester = FlameTester(_TestGame.new); - - flameTester.testGameWidget( - 'adds GameBonus.googleWord to the game when all letters are activated', - setUp: (game, tester) async { - await game.onLoad(); - final behavior = GoogleWordBonusBehavior(); - final parent = GoogleWord.test(); - final letters = [ - GoogleLetter(0), - GoogleLetter(1), - GoogleLetter(2), - GoogleLetter(3), - GoogleLetter(4), - GoogleLetter(5), - ]; - await parent.addAll(letters); - await game.pump(parent, gameBloc: gameBloc); - await parent.ensureAdd(behavior); - - for (final letter in letters) { - letter.bloc.onBallContacted(); - } - await tester.pump(); - - verify( - () => gameBloc.add(const BonusActivated(GameBonus.googleWord)), - ).called(1); - }, - ); - }); -} diff --git a/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart b/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart index 139c7e47..af77ef32 100644 --- a/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart +++ b/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart @@ -78,8 +78,24 @@ void main() { ); test( - 'is false when the bonusHistory has changed ' - 'with a bonus different than GameBonus.dashNest', () { + 'is true when the bonusHistory has changed ' + 'with a new GameBonus.googleWord', + () { + final previous = GameState.initial(); + final state = previous.copyWith( + bonusHistory: [GameBonus.googleWord], + ); + + expect( + MultiballsBehavior().listenWhen(previous, state), + isTrue, + ); + }, + ); + + test( + 'is false when the bonusHistory has changed with a bonus other than ' + 'GameBonus.dashNest or GameBonus.googleWord', () { final previous = GameState.initial().copyWith(bonusHistory: [GameBonus.dashNest]); final state = previous.copyWith( diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index ba54939c..289fb4fa 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -16,15 +16,19 @@ import 'package:pinball/game/game.dart'; import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/src/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:platform_helper/platform_helper.dart'; +import 'package:share_repository/share_repository.dart'; class _TestPinballGame extends PinballGame { _TestPinballGame() : super( characterThemeBloc: CharacterThemeCubit(), leaderboardRepository: _MockLeaderboardRepository(), + shareRepository: _MockShareRepository(), gameBloc: GameBloc(), l10n: _MockAppLocalizations(), audioPlayer: _MockPinballAudioPlayer(), + platformHelper: _MockPlatformHelper(), ); @override @@ -41,9 +45,11 @@ class _TestDebugPinballGame extends DebugPinballGame { : super( characterThemeBloc: CharacterThemeCubit(), leaderboardRepository: _MockLeaderboardRepository(), + shareRepository: _MockShareRepository(), gameBloc: GameBloc(), l10n: _MockAppLocalizations(), audioPlayer: _MockPinballAudioPlayer(), + platformHelper: _MockPlatformHelper(), ); @override @@ -81,8 +87,15 @@ class _MockDragEndInfo extends Mock implements DragEndInfo {} class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { } +class _MockShareRepository extends Mock implements ShareRepository {} + class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} +class _MockPlatformHelper extends Mock implements PlatformHelper { + @override + bool get isMobile => false; +} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -113,11 +126,11 @@ void main() { ); flameTester.test( - 'has only one BallThemingBehavior', + 'has only one CharacterSelectionBehavior', (game) async { await game.ready(); expect( - game.descendants().whereType().length, + game.descendants().whereType().length, equals(1), ); }, @@ -179,11 +192,11 @@ void main() { ); flameTester.test( - 'one GoogleWord', + 'one GoogleGallery', (game) async { await game.ready(); expect( - game.descendants().whereType().length, + game.descendants().whereType().length, equals(1), ); }, diff --git a/test/game/view/pinball_game_page_test.dart b/test/game/view/pinball_game_page_test.dart index b2cde26e..a0a0f22c 100644 --- a/test/game/view/pinball_game_page_test.dart +++ b/test/game/view/pinball_game_page_test.dart @@ -10,12 +10,13 @@ import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/assets_manager/assets_manager.dart'; import 'package:pinball/game/game.dart'; -import 'package:pinball/gen/gen.dart'; import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/more_information/more_information.dart'; import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/start_game.dart'; import 'package:pinball_audio/pinball_audio.dart'; +import 'package:platform_helper/platform_helper.dart'; +import 'package:share_repository/share_repository.dart'; import '../../helpers/helpers.dart'; @@ -24,9 +25,11 @@ class _TestPinballGame extends PinballGame { : super( characterThemeBloc: CharacterThemeCubit(), leaderboardRepository: _MockLeaderboardRepository(), + shareRepository: _MockShareRepository(), gameBloc: GameBloc(), l10n: _MockAppLocalizations(), audioPlayer: _MockPinballAudioPlayer(), + platformHelper: _MockPlatformHelper(), ); @override @@ -60,6 +63,13 @@ class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { } +class _MockShareRepository extends Mock implements ShareRepository {} + +class _MockPlatformHelper extends Mock implements PlatformHelper { + @override + bool get isMobile => false; +} + void main() { final game = _TestPinballGame(); @@ -312,7 +322,7 @@ void main() { gameBloc: gameBloc, startGameBloc: startGameBloc, ); - expect(find.image(Assets.images.linkBox.infoIcon), findsOneWidget); + expect(find.byIcon(Icons.info), findsOneWidget); }); testWidgets('opens MoreInformationDialog when tapped', (tester) async { diff --git a/test/game/view/widgets/play_button_overlay_test.dart b/test/game/view/widgets/play_button_overlay_test.dart index f10c5f5b..8980c72d 100644 --- a/test/game/view/widgets/play_button_overlay_test.dart +++ b/test/game/view/widgets/play_button_overlay_test.dart @@ -29,7 +29,7 @@ void main() { expect(find.text('Play'), findsOneWidget); }); - testWidgets('adds PlayTapped event to StartGameBloc when taped', + testWidgets('adds PlayTapped event to StartGameBloc when tapped', (tester) async { await tester.pumpApp( const PlayButtonOverlay(), diff --git a/test/helpers/pump_app.dart b/test/helpers/pump_app.dart index 8be03f03..d136487c 100644 --- a/test/helpers/pump_app.dart +++ b/test/helpers/pump_app.dart @@ -12,12 +12,16 @@ import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/start_game.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_ui/pinball_ui.dart'; +import 'package:platform_helper/platform_helper.dart'; +import 'package:share_repository/share_repository.dart'; class _MockAssetsManagerCubit extends Mock implements AssetsManagerCubit {} class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { } +class _MockShareRepository extends Mock implements ShareRepository {} + class _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {} class _MockGameBloc extends Mock implements GameBloc {} @@ -26,6 +30,8 @@ class _MockStartGameBloc extends Mock implements StartGameBloc {} class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} +class _MockPlatformHelper extends Mock implements PlatformHelper {} + PinballAudioPlayer _buildDefaultPinballAudioPlayer() { final audioPlayer = _MockPinballAudioPlayer(); when(audioPlayer.load).thenAnswer((_) => [Future.value()]); @@ -55,7 +61,9 @@ extension PumpApp on WidgetTester { AssetsManagerCubit? assetsManagerCubit, CharacterThemeCubit? characterThemeCubit, LeaderboardRepository? leaderboardRepository, + ShareRepository? shareRepository, PinballAudioPlayer? pinballAudioPlayer, + PlatformHelper? platformHelper, }) { return runAsync(() { return pumpWidget( @@ -64,9 +72,15 @@ extension PumpApp on WidgetTester { RepositoryProvider.value( value: leaderboardRepository ?? _MockLeaderboardRepository(), ), + RepositoryProvider.value( + value: shareRepository ?? _MockShareRepository(), + ), RepositoryProvider.value( value: pinballAudioPlayer ?? _buildDefaultPinballAudioPlayer(), ), + RepositoryProvider.value( + value: platformHelper ?? _MockPlatformHelper(), + ), ], child: MultiBlocProvider( providers: [ diff --git a/test/how_to_play/how_to_play_dialog_test.dart b/test/how_to_play/how_to_play_dialog_test.dart index c6e60d73..78e793cb 100644 --- a/test/how_to_play/how_to_play_dialog_test.dart +++ b/test/how_to_play/how_to_play_dialog_test.dart @@ -17,27 +17,23 @@ void main() { setUp(() async { l10n = await AppLocalizations.delegate.load(const Locale('en')); platformHelper = _MockPlatformHelper(); + when(() => platformHelper.isMobile).thenAnswer((_) => false); }); - testWidgets( - 'can be instantiated without passing in a platform helper', - (tester) async { - await tester.pumpApp( - HowToPlayDialog( - onDismissCallback: () {}, - ), - ); - expect(find.byType(HowToPlayDialog), findsOneWidget); - }, - ); + test('can be instantiated', () { + expect( + HowToPlayDialog(onDismissCallback: () {}), + isA(), + ); + }); testWidgets('displays content for desktop', (tester) async { when(() => platformHelper.isMobile).thenAnswer((_) => false); await tester.pumpApp( HowToPlayDialog( - platformHelper: platformHelper, onDismissCallback: () {}, ), + platformHelper: platformHelper, ); expect(find.text(l10n.howToPlay), findsOneWidget); expect(find.text(l10n.tipsForFlips), findsOneWidget); @@ -50,9 +46,9 @@ void main() { when(() => platformHelper.isMobile).thenAnswer((_) => true); await tester.pumpApp( HowToPlayDialog( - platformHelper: platformHelper, onDismissCallback: () {}, ), + platformHelper: platformHelper, ); expect(find.text(l10n.howToPlay), findsOneWidget); expect(find.text(l10n.tipsForFlips), findsOneWidget); @@ -75,6 +71,7 @@ void main() { ); }, ), + platformHelper: platformHelper, ); expect(find.byType(HowToPlayDialog), findsNothing); await tester.tap(find.text('test')); @@ -100,6 +97,7 @@ void main() { ); }, ), + platformHelper: platformHelper, ); expect(find.byType(HowToPlayDialog), findsNothing); await tester.tap(find.text('test')); diff --git a/test/more_information/more_information_dialog_test.dart b/test/more_information/more_information_dialog_test.dart index f87ec84c..cf7ba149 100644 --- a/test/more_information/more_information_dialog_test.dart +++ b/test/more_information/more_information_dialog_test.dart @@ -138,7 +138,7 @@ void main() { ); { - 'Open Source Code': 'https://github.com/VGVentures/pinball', + 'Open Source Code': 'https://github.com/flutter/pinball', 'Google I/O': 'https://events.google.com/io/', 'Flutter Games': 'http://flutter.dev/games', 'How it’s made': diff --git a/test/start_game/widgets/start_game_listener_test.dart b/test/start_game/widgets/start_game_listener_test.dart index d801864b..61171a40 100644 --- a/test/start_game/widgets/start_game_listener_test.dart +++ b/test/start_game/widgets/start_game_listener_test.dart @@ -7,6 +7,7 @@ import 'package:pinball/how_to_play/how_to_play.dart'; import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/start_game.dart'; import 'package:pinball_audio/pinball_audio.dart'; +import 'package:platform_helper/platform_helper.dart'; import '../../helpers/helpers.dart'; @@ -18,10 +19,16 @@ class _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {} class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} +class _MockPlatformHelper extends Mock implements PlatformHelper { + @override + bool get isMobile => false; +} + void main() { late StartGameBloc startGameBloc; late PinballAudioPlayer pinballAudioPlayer; late CharacterThemeCubit characterThemeCubit; + late PlatformHelper platformHelper; group('StartGameListener', () { setUp(() async { @@ -30,6 +37,7 @@ void main() { startGameBloc = _MockStartGameBloc(); pinballAudioPlayer = _MockPinballAudioPlayer(); characterThemeCubit = _MockCharacterThemeCubit(); + platformHelper = _MockPlatformHelper(); }); group('on selectCharacter status', () { @@ -121,6 +129,7 @@ void main() { child: SizedBox.shrink(), ), startGameBloc: startGameBloc, + platformHelper: platformHelper, ); await tester.pumpAndSettle(); @@ -213,6 +222,7 @@ void main() { child: SizedBox.shrink(), ), startGameBloc: startGameBloc, + platformHelper: platformHelper, ); await tester.pumpAndSettle(); @@ -244,6 +254,7 @@ void main() { ), startGameBloc: startGameBloc, pinballAudioPlayer: pinballAudioPlayer, + platformHelper: platformHelper, ); await tester.pumpAndSettle();