diff --git a/lib/game/view/game_hud.dart b/lib/game/view/game_hud.dart new file mode 100644 index 00000000..00eedd2b --- /dev/null +++ b/lib/game/view/game_hud.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:pinball/game/game.dart'; + +/// {@template game_hud} +/// Overlay of a [PinballGame] that displays the current [GameState.score] and +/// [GameState.balls]. +/// {@endtemplate} +class GameHud extends StatelessWidget { + /// {@macro game_hud} + const GameHud({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final state = context.watch().state; + + return Container( + color: Colors.redAccent, + width: 200, + height: 100, + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${state.score}', + style: Theme.of(context).textTheme.headline3, + ), + Wrap( + direction: Axis.vertical, + children: [ + for (var i = 0; i < state.balls; i++) + const Padding( + padding: EdgeInsets.only(top: 6, right: 6), + child: CircleAvatar( + radius: 8, + backgroundColor: Colors.black, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/game/view/pinball_game_page.dart b/lib/game/view/pinball_game_page.dart index 02f5b34c..8a9a981c 100644 --- a/lib/game/view/pinball_game_page.dart +++ b/lib/game/view/pinball_game_page.dart @@ -56,7 +56,18 @@ class _PinballGameViewState extends State { ); } }, - child: GameWidget(game: _game), + child: Stack( + children: [ + Positioned.fill( + child: GameWidget(game: _game), + ), + const Positioned( + top: 8, + left: 8, + child: GameHud(), + ), + ], + ), ); } } diff --git a/lib/game/view/view.dart b/lib/game/view/view.dart index 53d3813a..26b700d3 100644 --- a/lib/game/view/view.dart +++ b/lib/game/view/view.dart @@ -1,2 +1,3 @@ +export 'game_hud.dart'; export 'pinball_game_page.dart'; export 'widgets/widgets.dart'; diff --git a/test/game/view/game_hud_test.dart b/test/game/view/game_hud_test.dart new file mode 100644 index 00000000..e7334e41 --- /dev/null +++ b/test/game/view/game_hud_test.dart @@ -0,0 +1,79 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/game/game.dart'; +import '../../helpers/helpers.dart'; + +void main() { + group('GameHud', () { + late GameBloc gameBloc; + const initialState = GameState(score: 10, balls: 2, bonusLetters: []); + + void _mockState(GameState state) { + whenListen( + gameBloc, + Stream.value(state), + initialState: state, + ); + } + + Future _pumpHud(WidgetTester tester) async { + await tester.pumpApp( + GameHud(), + gameBloc: gameBloc, + ); + } + + setUp(() { + gameBloc = MockGameBloc(); + _mockState(initialState); + }); + + testWidgets( + 'renders the current score', + (tester) async { + await _pumpHud(tester); + expect(find.text(initialState.score.toString()), findsOneWidget); + }, + ); + + testWidgets( + 'renders the current ball number', + (tester) async { + await _pumpHud(tester); + expect( + find.byType(CircleAvatar), + findsNWidgets(initialState.balls), + ); + }, + ); + + testWidgets('updates the score', (tester) async { + await _pumpHud(tester); + expect(find.text(initialState.score.toString()), findsOneWidget); + + _mockState(initialState.copyWith(score: 20)); + + await tester.pump(); + expect(find.text('20'), findsOneWidget); + }); + + testWidgets('updates the ball number', (tester) async { + await _pumpHud(tester); + expect( + find.byType(CircleAvatar), + findsNWidgets(initialState.balls), + ); + + _mockState(initialState.copyWith(balls: 1)); + + await tester.pump(); + expect( + find.byType(CircleAvatar), + findsNWidgets(1), + ); + }); + }); +} diff --git a/test/game/view/pinball_game_page_test.dart b/test/game/view/pinball_game_page_test.dart index d578a1db..746dc2c7 100644 --- a/test/game/view/pinball_game_page_test.dart +++ b/test/game/view/pinball_game_page_test.dart @@ -48,7 +48,7 @@ void main() { }); group('PinballGameView', () { - testWidgets('renders game', (tester) async { + testWidgets('renders game and a hud', (tester) async { final gameBloc = MockGameBloc(); whenListen( gameBloc, @@ -61,6 +61,10 @@ void main() { find.byWidgetPredicate((w) => w is GameWidget), findsOneWidget, ); + expect( + find.byType(GameHud), + findsOneWidget, + ); }); testWidgets(