Merge branch 'main' into refactor/sparky-computer

pull/365/head
Allison Ryan 3 years ago
commit 4c342d5144

@ -1,76 +0,0 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_ui/pinball_ui.dart';
/// {@template footer}
/// Footer widget with links to the main tech stack.
/// {@endtemplate}
class Footer extends StatelessWidget {
/// {@macro footer}
const Footer({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(50, 0, 50, 32),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
_MadeWithFlutterAndFirebase(),
_GoogleIO(),
],
),
);
}
}
class _GoogleIO extends StatelessWidget {
const _GoogleIO({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final theme = Theme.of(context);
return Text(
l10n.footerGoogleIOText,
style: theme.textTheme.bodyText1!.copyWith(color: PinballColors.white),
);
}
}
class _MadeWithFlutterAndFirebase extends StatelessWidget {
const _MadeWithFlutterAndFirebase({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final theme = Theme.of(context);
return RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: l10n.footerMadeWithText,
style: theme.textTheme.bodyText1!.copyWith(color: PinballColors.white),
children: <TextSpan>[
TextSpan(
text: l10n.footerFlutterLinkText,
recognizer: TapGestureRecognizer()
..onTap = () => openLink('https://flutter.dev'),
style: const TextStyle(
decoration: TextDecoration.underline,
),
),
const TextSpan(text: ' & '),
TextSpan(
text: l10n.footerFirebaseLinkText,
recognizer: TapGestureRecognizer()
..onTap = () => openLink('https://firebase.google.com'),
style: const TextStyle(
decoration: TextDecoration.underline,
),
),
],
),
);
}
}

@ -1,4 +1,5 @@
export 'ball_spawning_behavior.dart';
export 'bonus_noise_behavior.dart';
export 'bumper_noise_behavior.dart';
export 'camera_focusing_behavior.dart';
export 'scoring_behavior.dart';

@ -0,0 +1,41 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// Behavior that handles playing a bonus sound effect
class BonusNoiseBehavior extends Component {
@override
Future<void> onLoad() async {
await add(
FlameBlocListener<GameBloc, GameState>(
listenWhen: (previous, current) {
return previous.bonusHistory.length != current.bonusHistory.length;
},
onNewState: (state) {
final bonus = state.bonusHistory.last;
final audioPlayer = readProvider<PinballPlayer>();
switch (bonus) {
case GameBonus.googleWord:
audioPlayer.play(PinballAudio.google);
break;
case GameBonus.sparkyTurboCharge:
audioPlayer.play(PinballAudio.sparky);
break;
case GameBonus.dinoChomp:
// TODO(erickzanardo): Add sound
break;
case GameBonus.androidSpaceship:
// TODO(erickzanardo): Add sound
break;
case GameBonus.dashNest:
// TODO(erickzanardo): Add sound
break;
}
},
),
);
}
}

@ -1,7 +1,6 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.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';
@ -22,7 +21,6 @@ class GoogleWordBonusBehavior extends Component
.every((letter) => letter.bloc.state == GoogleLetterState.lit);
if (achievedBonus) {
readProvider<PinballPlayer>().play(PinballAudio.google);
bloc.add(const BonusActivated(GameBonus.googleWord));
for (final letter in googleLetters) {
letter.bloc.onReset();

@ -24,7 +24,8 @@ class PinballGame extends PinballForge2DGame
required GameBloc gameBloc,
required AppLocalizations l10n,
required PinballPlayer player,
}) : _gameBloc = gameBloc,
}) : focusNode = FocusNode(),
_gameBloc = gameBloc,
_player = player,
_characterTheme = characterTheme,
_l10n = l10n,
@ -40,6 +41,8 @@ class PinballGame extends PinballForge2DGame
@override
Color backgroundColor() => Colors.transparent;
final FocusNode focusNode;
final CharacterTheme _characterTheme;
final PinballPlayer _player;
@ -64,6 +67,7 @@ class PinballGame extends PinballForge2DGame
FlameProvider<AppLocalizations>.value(_l10n),
],
children: [
BonusNoiseBehavior(),
GameBlocStatusListener(),
BallSpawningBehavior(),
CameraFocusingBehavior(),

@ -7,7 +7,9 @@ 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';
@ -118,22 +120,31 @@ class PinballGameLoadedView extends StatelessWidget {
child: Stack(
children: [
Positioned.fill(
child: GameWidget<PinballGame>(
game: game,
initialActiveOverlays: const [PinballGame.playButtonOverlay],
overlayBuilderMap: {
PinballGame.playButtonOverlay: (context, game) {
return const Positioned(
bottom: 20,
right: 0,
left: 0,
child: PlayButtonOverlay(),
);
},
child: MouseRegion(
onHover: (_) {
if (!game.focusNode.hasFocus) {
game.focusNode.requestFocus();
}
},
child: GameWidget<PinballGame>(
game: game,
focusNode: game.focusNode,
initialActiveOverlays: const [PinballGame.playButtonOverlay],
overlayBuilderMap: {
PinballGame.playButtonOverlay: (context, game) {
return const Positioned(
bottom: 20,
right: 0,
left: 0,
child: PlayButtonOverlay(),
);
},
},
),
),
),
const _PositionedGameHud(),
const _PositionedInfoIcon(),
],
),
);
@ -151,6 +162,7 @@ class _PositionedGameHud extends StatelessWidget {
final isGameOver = context.select(
(GameBloc bloc) => bloc.state.status.isGameOver,
);
final gameWidgetWidth = MediaQuery.of(context).size.height * 9 / 16;
final screenWidth = MediaQuery.of(context).size.width;
final leftMargin = (screenWidth / 2) - (gameWidgetWidth / 1.8);
@ -166,3 +178,27 @@ class _PositionedGameHud extends StatelessWidget {
);
}
}
class _PositionedInfoIcon extends StatelessWidget {
const _PositionedInfoIcon({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Positioned(
top: 0,
left: 0,
child: BlocBuilder<GameBloc, GameState>(
builder: (context, state) {
return Visibility(
visible: state.status.isGameOver,
child: IconButton(
iconSize: 50,
icon: Assets.images.linkBox.infoIcon.image(),
onPressed: () => showMoreInformationDialog(context),
),
);
},
),
);
}
}

@ -104,22 +104,6 @@
"@toSubmit": {
"description": "Ending text displayed on initials input screen informational text span"
},
"footerMadeWithText": "Made with ",
"@footerMadeWithText": {
"description": "Text shown on the footer which mentions technologies used to build the app."
},
"footerFlutterLinkText": "Flutter",
"@footerFlutterLinkText": {
"description": "Text on the link shown on the footer which navigates to the Flutter page"
},
"footerFirebaseLinkText": "Firebase",
"@footerFirebaseLinkText": {
"description": "Text on the link shown on the footer which navigates to the Firebase page"
},
"footerGoogleIOText": "Google I/O",
"@footerGoogleIOText": {
"description": "Text shown on the footer which mentions Google I/O"
},
"linkBoxTitle": "Resources",
"@linkBoxTitle": {
"description": "Text shown on the link box title section."

@ -0,0 +1 @@
export 'more_information_dialog.dart';

@ -0,0 +1,218 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_ui/pinball_ui.dart';
/// Inflates [MoreInformationDialog] using [showDialog].
Future<void> showMoreInformationDialog(BuildContext context) {
final gameWidgetWidth = MediaQuery.of(context).size.height * 9 / 16;
return showDialog<void>(
context: context,
barrierColor: PinballColors.transparent,
barrierDismissible: true,
builder: (_) {
return Center(
child: SizedBox(
height: gameWidgetWidth * 0.87,
width: gameWidgetWidth,
child: const MoreInformationDialog(),
),
);
},
);
}
/// {@template more_information_dialog}
/// Dialog used to show informational links
/// {@endtemplate}
class MoreInformationDialog extends StatelessWidget {
/// {@macro more_information_dialog}
const MoreInformationDialog({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
color: PinballColors.transparent,
child: _LinkBoxDecoration(
child: Column(
children: const [
SizedBox(height: 16),
_LinkBoxHeader(),
Expanded(
child: _LinkBoxBody(),
),
],
),
),
);
}
}
class _LinkBoxHeader extends StatelessWidget {
const _LinkBoxHeader({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final indent = MediaQuery.of(context).size.width / 5;
return Column(
children: [
Text(
l10n.linkBoxTitle,
style: Theme.of(context).textTheme.headline3!.copyWith(
color: PinballColors.blue,
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 12),
Divider(
color: PinballColors.white,
endIndent: indent,
indent: indent,
thickness: 2,
),
],
);
}
}
class _LinkBoxDecoration extends StatelessWidget {
const _LinkBoxDecoration({
Key? key,
required this.child,
}) : super(key: key);
final Widget child;
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: const CrtBackground().copyWith(
borderRadius: const BorderRadius.all(Radius.circular(12)),
border: Border.all(
color: PinballColors.white,
width: 5,
),
),
child: child,
);
}
}
class _LinkBoxBody extends StatelessWidget {
const _LinkBoxBody({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const _MadeWithFlutterAndFirebase(),
_TextLink(
text: l10n.linkBoxOpenSourceCode,
link: _MoreInformationUrl.openSourceCode,
),
_TextLink(
text: l10n.linkBoxGoogleIOText,
link: _MoreInformationUrl.googleIOEvent,
),
_TextLink(
text: l10n.linkBoxFlutterGames,
link: _MoreInformationUrl.flutterGamesWebsite,
),
_TextLink(
text: l10n.linkBoxHowItsMade,
link: _MoreInformationUrl.howItsMadeArticle,
),
_TextLink(
text: l10n.linkBoxTermsOfService,
link: _MoreInformationUrl.termsOfService,
),
_TextLink(
text: l10n.linkBoxPrivacyPolicy,
link: _MoreInformationUrl.privacyPolicy,
),
],
);
}
}
class _TextLink extends StatelessWidget {
const _TextLink({
Key? key,
required this.text,
required this.link,
}) : super(key: key);
final String text;
final String link;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return InkWell(
onTap: () => openLink(link),
child: Text(
text,
style: theme.textTheme.headline5!.copyWith(
color: PinballColors.white,
),
overflow: TextOverflow.ellipsis,
),
);
}
}
class _MadeWithFlutterAndFirebase extends StatelessWidget {
const _MadeWithFlutterAndFirebase({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final theme = Theme.of(context);
return RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: l10n.linkBoxMadeWithText,
style: theme.textTheme.headline5!.copyWith(color: PinballColors.white),
children: <TextSpan>[
TextSpan(
text: l10n.linkBoxFlutterLinkText,
recognizer: TapGestureRecognizer()
..onTap = () => openLink(_MoreInformationUrl.flutterWebsite),
style: const TextStyle(
decoration: TextDecoration.underline,
),
),
const TextSpan(text: ' & '),
TextSpan(
text: l10n.linkBoxFirebaseLinkText,
recognizer: TapGestureRecognizer()
..onTap = () => openLink(_MoreInformationUrl.firebaseWebsite),
style: theme.textTheme.headline5!.copyWith(
decoration: TextDecoration.underline,
),
),
],
),
);
}
}
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 googleIOEvent = 'https://events.google.com/io/';
static const flutterGamesWebsite = 'http://flutter.dev/games';
static const howItsMadeArticle =
'https://medium.com/flutter/i-o-pinball-powered-by-flutter-and-firebase-d22423f3f5d';
static const termsOfService = 'https://policies.google.com/terms';
static const privacyPolicy = 'https://policies.google.com/privacy';
}

@ -14,13 +14,13 @@ class $AssetsMusicGen {
class $AssetsSfxGen {
const $AssetsSfxGen();
String get afterLaunch => 'assets/sfx/after_launch.mp3';
String get bumperA => 'assets/sfx/bumper_a.mp3';
String get bumperB => 'assets/sfx/bumper_b.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 launcher => 'assets/sfx/launcher.mp3';
String get sparky => 'assets/sfx/sparky.mp3';
}
class Assets {

@ -25,6 +25,9 @@ enum PinballAudio {
/// Launcher
launcher,
/// Sparky
sparky,
}
/// Defines the contract of the creation of an [AudioPool].
@ -161,6 +164,11 @@ class PinballPlayer {
playSingleAudio: _playSingleAudio,
path: Assets.sfx.google,
),
PinballAudio.sparky: _SimplePlayAudio(
preCacheSingleAudio: _preCacheSingleAudio,
playSingleAudio: _playSingleAudio,
path: Assets.sfx.sparky,
),
PinballAudio.launcher: _SimplePlayAudio(
preCacheSingleAudio: _preCacheSingleAudio,
playSingleAudio: _playSingleAudio,

@ -141,6 +141,10 @@ void main() {
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/google.mp3'),
).called(1);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/sparky.mp3'),
).called(1);
verify(
() => preCacheSingleAudio.onCall(
'packages/pinball_audio/assets/sfx/io_pinball_voice_over.mp3',
@ -211,7 +215,7 @@ void main() {
});
});
group('googleBonus', () {
group('google', () {
test('plays the correct file', () async {
await Future.wait(player.load());
player.play(PinballAudio.google);
@ -223,6 +227,18 @@ void main() {
});
});
group('sparky', () {
test('plays the correct file', () async {
await Future.wait(player.load());
player.play(PinballAudio.sparky);
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.sparky}'),
).called(1);
});
});
group('launcher', () {
test('plays the correct file', () async {
await Future.wait(player.load());

@ -11,7 +11,7 @@ dependencies:
flame: ^1.1.1
flame_forge2d:
git:
url: https://github.com/flame-engine/flame/
url: https://github.com/flame-engine/flame
path: packages/flame_forge2d/
ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f
flutter:

@ -112,7 +112,7 @@ packages:
path: "packages/flame_forge2d"
ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f
resolved-ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f
url: "https://github.com/flame-engine/flame/"
url: "https://github.com/flame-engine/flame"
source: git
version: "0.11.0"
flutter:

@ -11,7 +11,7 @@ dependencies:
flame: ^1.1.1
flame_forge2d:
git:
url: https://github.com/flame-engine/flame/
url: https://github.com/flame-engine/flame
path: packages/flame_forge2d/
ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f
flutter:

@ -1,4 +1,5 @@
import 'dart:ui';
import 'package:collection/collection.dart' as collection;
import 'package:flame/components.dart';
import 'package:pinball_flame/src/canvas/canvas_wrapper.dart';
@ -56,7 +57,14 @@ class _ZCanvas extends CanvasWrapper {
final List<ZIndex> _zBuffer = [];
/// Postpones the rendering of [ZIndex] component and its children.
void buffer(ZIndex component) => _zBuffer.add(component);
void buffer(ZIndex component) {
final lowerBound = collection.lowerBound<ZIndex>(
_zBuffer,
component,
compare: (a, b) => a.zIndex.compareTo(b.zIndex),
);
_zBuffer.insert(lowerBound, component);
}
/// Renders all [ZIndex] components and their children.
///
@ -69,8 +77,7 @@ class _ZCanvas extends CanvasWrapper {
/// before the second one.
/// {@endtemplate}
void render() => _zBuffer
..sort((a, b) => a.zIndex.compareTo(b.zIndex))
..whereType<Component>().forEach(_render)
..forEach(_render)
..clear();
void _render(Component component) => component.renderTree(canvas);

@ -10,7 +10,7 @@ dependencies:
flame: ^1.1.1
flame_forge2d:
git:
url: https://github.com/flame-engine/flame/
url: https://github.com/flame-engine/flame
path: packages/flame_forge2d/
ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f
flutter:

@ -245,7 +245,7 @@ packages:
path: "packages/flame_forge2d"
ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f
resolved-ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f
url: "https://github.com/flame-engine/flame/"
url: "https://github.com/flame-engine/flame"
source: git
version: "0.11.0"
flame_test:

@ -18,7 +18,7 @@ dependencies:
flame_bloc: ^1.4.0
flame_forge2d:
git:
url: https://github.com/flame-engine/flame/
url: https://github.com/flame-engine/flame
path: packages/flame_forge2d/
ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f
flutter:

@ -0,0 +1,186 @@
// ignore_for_file: cascade_invocations
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/game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_flame/pinball_flame.dart';
class _TestGame extends Forge2DGame {
Future<void> pump(
BonusNoiseBehavior child, {
required PinballPlayer player,
required GameBloc bloc,
}) {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: bloc,
children: [
FlameProvider<PinballPlayer>.value(
player,
children: [
child,
],
),
],
),
);
}
}
class _MockPinballPlayer extends Mock implements PinballPlayer {}
class _MockGameBloc extends Mock implements GameBloc {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('BonusNoiseBehavior', () {
late PinballPlayer player;
late GameBloc bloc;
final flameTester = FlameTester(_TestGame.new);
setUpAll(() {
registerFallbackValue(PinballAudio.google);
});
setUp(() {
player = _MockPinballPlayer();
when(() => player.play(any())).thenAnswer((_) {});
bloc = _MockGameBloc();
});
flameTester.testGameWidget(
'plays google sound',
setUp: (game, _) async {
const state = GameState(
totalScore: 0,
roundScore: 0,
multiplier: 1,
rounds: 0,
bonusHistory: [GameBonus.googleWord],
status: GameStatus.playing,
);
const initialState = GameState.initial();
whenListen(
bloc,
Stream.fromIterable([initialState, state]),
initialState: initialState,
);
final behavior = BonusNoiseBehavior();
await game.pump(behavior, player: player, bloc: bloc);
},
verify: (_, __) async {
verify(() => player.play(PinballAudio.google)).called(1);
},
);
flameTester.testGameWidget(
'plays sparky sound',
setUp: (game, _) async {
const state = GameState(
totalScore: 0,
roundScore: 0,
multiplier: 1,
rounds: 0,
bonusHistory: [GameBonus.sparkyTurboCharge],
status: GameStatus.playing,
);
const initialState = GameState.initial();
whenListen(
bloc,
Stream.fromIterable([initialState, state]),
initialState: initialState,
);
final behavior = BonusNoiseBehavior();
await game.pump(behavior, player: player, bloc: bloc);
},
verify: (_, __) async {
verify(() => player.play(PinballAudio.sparky)).called(1);
},
);
flameTester.testGameWidget(
'plays dino chomp sound',
setUp: (game, _) async {
const state = GameState(
totalScore: 0,
roundScore: 0,
multiplier: 1,
rounds: 0,
bonusHistory: [GameBonus.dinoChomp],
status: GameStatus.playing,
);
const initialState = GameState.initial();
whenListen(
bloc,
Stream.fromIterable([initialState, state]),
initialState: initialState,
);
final behavior = BonusNoiseBehavior();
await game.pump(behavior, player: player, bloc: bloc);
},
verify: (_, __) async {
// TODO(erickzanardo): Change when the sound is implemented
verifyNever(() => player.play(any()));
},
);
flameTester.testGameWidget(
'plays android spaceship sound',
setUp: (game, _) async {
const state = GameState(
totalScore: 0,
roundScore: 0,
multiplier: 1,
rounds: 0,
bonusHistory: [GameBonus.androidSpaceship],
status: GameStatus.playing,
);
const initialState = GameState.initial();
whenListen(
bloc,
Stream.fromIterable([initialState, state]),
initialState: initialState,
);
final behavior = BonusNoiseBehavior();
await game.pump(behavior, player: player, bloc: bloc);
},
verify: (_, __) async {
// TODO(erickzanardo): Change when the sound is implemented
verifyNever(() => player.play(any()));
},
);
flameTester.testGameWidget(
'plays dash nest sound',
setUp: (game, _) async {
const state = GameState(
totalScore: 0,
roundScore: 0,
multiplier: 1,
rounds: 0,
bonusHistory: [GameBonus.dashNest],
status: GameStatus.playing,
);
const initialState = GameState.initial();
whenListen(
bloc,
Stream.fromIterable([initialState, state]),
initialState: initialState,
);
final behavior = BonusNoiseBehavior();
await game.pump(behavior, player: player, bloc: bloc);
},
verify: (_, __) async {
// TODO(erickzanardo): Change when the sound is implemented
verifyNever(() => player.play(any()));
},
);
});
}

@ -1,5 +1,7 @@
// ignore_for_file: prefer_const_constructors
import 'dart:ui';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
@ -8,7 +10,9 @@ 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';
@ -291,5 +295,90 @@ void main() {
findsNothing,
);
});
testWidgets('keep focus on game when mouse hovers over it', (tester) async {
final startGameState = StartGameState.initial().copyWith(
status: StartGameStatus.play,
);
final gameState = GameState.initial().copyWith(
status: GameStatus.gameOver,
);
whenListen(
startGameBloc,
Stream.value(startGameState),
initialState: startGameState,
);
whenListen(
gameBloc,
Stream.value(gameState),
initialState: gameState,
);
await tester.pumpApp(
PinballGameView(game: game),
gameBloc: gameBloc,
startGameBloc: startGameBloc,
);
game.focusNode.unfocus();
await tester.pump();
expect(game.focusNode.hasFocus, isFalse);
final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(location: Offset.zero);
addTearDown(gesture.removePointer);
await gesture.moveTo((game.size / 2).toOffset());
await tester.pump();
expect(game.focusNode.hasFocus, isTrue);
});
group('info icon', () {
testWidgets('renders on game over', (tester) async {
final gameState = GameState.initial().copyWith(
status: GameStatus.gameOver,
);
whenListen(
gameBloc,
Stream.value(gameState),
initialState: gameState,
);
await tester.pumpApp(
PinballGameView(game: game),
gameBloc: gameBloc,
startGameBloc: startGameBloc,
);
expect(
find.image(Assets.images.linkBox.infoIcon),
findsOneWidget,
);
});
testWidgets('opens MoreInformationDialog when tapped', (tester) async {
final gameState = GameState.initial().copyWith(
status: GameStatus.gameOver,
);
whenListen(
gameBloc,
Stream.value(gameState),
initialState: gameState,
);
await tester.pumpApp(
PinballGameView(game: game),
gameBloc: gameBloc,
startGameBloc: startGameBloc,
);
await tester.tap(find.byType(IconButton));
await tester.pump();
expect(
find.byType(MoreInformationDialog),
findsOneWidget,
);
});
});
});
}

@ -2,7 +2,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/footer/footer.dart';
import 'package:pinball/more_information/more_information.dart';
import 'package:pinball_ui/pinball_ui.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
@ -28,15 +28,34 @@ class _MockUrlLauncher extends Mock
implements UrlLauncherPlatform {}
void main() {
group('Footer', () {
group('MoreInformationDialog', () {
late UrlLauncherPlatform urlLauncher;
setUp(() async {
urlLauncher = _MockUrlLauncher();
UrlLauncherPlatform.instance = urlLauncher;
});
group('showMoreInformationDialog', () {
testWidgets('inflates the dialog', (tester) async {
await tester.pumpApp(
Builder(
builder: (context) {
return TextButton(
onPressed: () => showMoreInformationDialog(context),
child: const Text('test'),
);
},
),
);
await tester.tap(find.text('test'));
await tester.pump();
expect(find.byType(MoreInformationDialog), findsOneWidget);
});
});
testWidgets('renders "Made with..." and "Google I/O"', (tester) async {
await tester.pumpApp(const Footer());
await tester.pumpApp(const MoreInformationDialog());
expect(find.text('Google I/O'), findsOneWidget);
expect(
find.byWidgetPredicate(
@ -63,7 +82,7 @@ void main() {
headers: any(named: 'headers'),
),
).thenAnswer((_) async => true);
await tester.pumpApp(const Footer());
await tester.pumpApp(const MoreInformationDialog());
final flutterTextFinder = find.byWidgetPredicate(
(widget) => widget is RichText && _tapTextSpan(widget, 'Flutter'),
);
@ -98,7 +117,7 @@ void main() {
headers: any(named: 'headers'),
),
).thenAnswer((_) async => true);
await tester.pumpApp(const Footer());
await tester.pumpApp(const MoreInformationDialog());
final firebaseTextFinder = find.byWidgetPredicate(
(widget) => widget is RichText && _tapTextSpan(widget, 'Firebase'),
);
@ -117,5 +136,50 @@ void main() {
);
},
);
<String, String>{
'Open Source Code': 'https://github.com/VGVentures/pinball',
'Google I/O': 'https://events.google.com/io/',
'Flutter Games': 'http://flutter.dev/games',
'How its made':
'https://medium.com/flutter/i-o-pinball-powered-by-flutter-and-firebase-d22423f3f5d',
'Terms of Service': 'https://policies.google.com/terms',
'Privacy Policy': 'https://policies.google.com/privacy',
}.forEach((text, link) {
testWidgets(
'tapping on "$text" opens the link - $link',
(tester) async {
when(() => urlLauncher.canLaunch(any()))
.thenAnswer((_) async => true);
when(
() => urlLauncher.launch(
link,
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);
await tester.pumpApp(const MoreInformationDialog());
await tester.tap(find.text(text));
await tester.pumpAndSettle();
verify(
() => urlLauncher.launch(
link,
useSafariVC: any(named: 'useSafariVC'),
useWebView: any(named: 'useWebView'),
enableJavaScript: any(named: 'enableJavaScript'),
enableDomStorage: any(named: 'enableDomStorage'),
universalLinksOnly: any(named: 'universalLinksOnly'),
headers: any(named: 'headers'),
),
);
},
);
});
});
}
Loading…
Cancel
Save