pull/254/head
arturplaczek 3 years ago
parent ef0dca403f
commit 56e248edfb

@ -5,8 +5,10 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball/theme/theme.dart';
import 'package:pinball_audio/pinball_audio.dart';
class PinballGamePage extends StatelessWidget {
@ -44,6 +46,8 @@ class PinballGamePage extends StatelessWidget {
...game.preLoadAssets(),
pinballAudio.load(),
...BonusAnimation.loadAssets(),
...SelectedCharacter.loadAssets(context),
...StarAnimation.loadAssets(),
];
return MultiBlocProvider(
@ -113,17 +117,13 @@ class PinballGameLoadedView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isPlaying = context.select(
(StartGameBloc bloc) => bloc.state.status == StartGameStatus.play,
);
final gameWidgetWidth = MediaQuery.of(context).size.height * 9 / 16;
final screenWidth = MediaQuery.of(context).size.width;
final leftMargin = (screenWidth / 2) - (gameWidgetWidth / 1.8);
return StartGameListener(
game: game,
child: Stack(
children: [
const Positioned.fill(
child: _PinballBackground(),
),
Positioned.fill(
child: GameWidget<PinballGame>(
game: game,
@ -134,22 +134,82 @@ class PinballGameLoadedView extends StatelessWidget {
bottom: 20,
right: 0,
left: 0,
child: PlayButtonOverlay(),
child: _StartGameButton(),
);
},
},
),
),
Positioned(
const _PinballGameHud(),
],
),
);
}
}
class _PinballBackground extends StatelessWidget {
const _PinballBackground({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final characterTheme = context.select(
(CharacterThemeCubit bloc) => bloc.state.characterTheme,
);
final isStarted = context.select(
(StartGameBloc bloc) => bloc.state.status != StartGameStatus.initial,
);
return Visibility(
visible: isStarted,
child: characterTheme.background.image(fit: BoxFit.fill),
);
}
}
class _StartGameButton extends StatelessWidget {
const _StartGameButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final isStarted = context.select(
(StartGameBloc bloc) => bloc.state.status != StartGameStatus.initial,
);
return isStarted
? const SizedBox.shrink()
: PinballButton(
child: Text(
l10n.start,
style: AppTextStyle.headline3,
),
onPressed: () {
context.read<StartGameBloc>().add(const PlayTapped());
},
);
}
}
class _PinballGameHud extends StatelessWidget {
const _PinballGameHud({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final isPlaying = context.select(
(StartGameBloc bloc) => bloc.state.status == StartGameStatus.play,
);
final gameWidgetWidth = MediaQuery.of(context).size.height * 9 / 16;
final screenWidth = MediaQuery.of(context).size.width;
final leftMargin = (screenWidth / 2) - (gameWidgetWidth / 1.8);
return Positioned(
top: 16,
left: leftMargin,
child: Visibility(
visible: isPlaying,
child: const GameHud(),
),
),
],
),
);
}
}

@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/leaderboard/leaderboard.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_theme/pinball_theme.dart';
class LeaderboardPage extends StatelessWidget {
@ -68,9 +68,8 @@ class LeaderboardView extends StatelessWidget {
),
const SizedBox(height: 20),
TextButton(
onPressed: () => Navigator.of(context).push<void>(
CharacterSelectionDialog.route(),
),
onPressed: () =>
context.read<StartGameBloc>().add(const PlayTapped()),
child: Text(l10n.retry),
),
],

@ -0,0 +1,171 @@
// ignore_for_file: public_member_api_docs
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball/theme/theme.dart';
import 'package:pinball_theme/pinball_theme.dart' hide Assets;
import 'package:pinball_ui/pinball_ui.dart';
class CharacterSelectionDialog extends StatelessWidget {
const CharacterSelectionDialog({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const CharacterSelectionView();
}
}
class CharacterSelectionView extends StatelessWidget {
const CharacterSelectionView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return PixelatedDecoration(
header: const _CharacterSelectionTitle(),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
SizedBox(height: 20),
Expanded(child: _CharacterSelectionBody()),
_SelectCharacter(),
SizedBox(height: 20),
],
),
);
}
}
class _CharacterSelectionTitle extends StatelessWidget {
const _CharacterSelectionTitle({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
l10n.characterSelectionTitle,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: AppTextStyle.headline2.copyWith(
fontWeight: FontWeight.bold,
color: AppColors.darkBlue,
),
),
Text(
l10n.characterSelectionSubtitle,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: AppTextStyle.headline3.copyWith(
color: AppColors.darkBlue,
),
),
],
);
}
}
class _CharacterSelectionBody extends StatelessWidget {
const _CharacterSelectionBody({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
const padding = EdgeInsets.symmetric(horizontal: 32);
return Row(
children: [
const Expanded(
child: Padding(
padding: padding,
child: Center(child: SelectedCharacter()),
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
StarAnimation.starA(),
const Padding(
padding: padding,
child: _CharacterSelection(),
),
],
),
),
],
);
}
}
class _CharacterSelection extends StatelessWidget {
const _CharacterSelection({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
children: const [
CharacterIcon(
DashTheme(),
key: Key('characterSelectionPage_dashButton'),
),
CharacterIcon(
SparkyTheme(),
key: Key('characterSelectionPage_sparkyButton'),
),
CharacterIcon(
AndroidTheme(),
key: Key('characterSelectionPage_androidButton'),
),
CharacterIcon(
DinoTheme(),
key: Key('characterSelectionPage_dinoButton'),
),
],
);
}
}
class _SelectCharacter extends StatelessWidget {
const _SelectCharacter({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
const Spacer(flex: 5),
const _SelectCharacterButton(),
const Spacer(flex: 2),
StarAnimation.starA(),
const Spacer(flex: 2),
],
);
}
}
class _SelectCharacterButton extends StatelessWidget {
const _SelectCharacterButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return PinballButton(
child: Text(
l10n.select,
style: AppTextStyle.headline5,
),
onPressed: () {
context.read<StartGameBloc>().add(const CharacterSelected());
Navigator.of(context).pop();
},
);
}
}

@ -1,3 +1,3 @@
export 'character_selection_dialog.dart';
export 'cubit/character_theme_cubit.dart';
export 'view/view.dart';
export 'widgets/widgets.dart';

@ -24,7 +24,7 @@ class SelectedCharacter extends StatefulWidget {
State<SelectedCharacter> createState() => _SelectedCharacterState();
/// Returns a list of assets to be loaded.
static List<Future> loadAssets() {
static List<Future> loadAssets(BuildContext context) {
Flame.images.prefix = '';
const dashTheme = DashTheme();
@ -41,6 +41,10 @@ class SelectedCharacter extends StatefulWidget {
Flame.images.load(androidTheme.background.keyName),
Flame.images.load(dinoTheme.background.keyName),
Flame.images.load(sparkyTheme.background.keyName),
precacheImage(AssetImage(dashTheme.background.keyName), context),
precacheImage(AssetImage(androidTheme.background.keyName), context),
precacheImage(AssetImage(dinoTheme.background.keyName), context),
precacheImage(AssetImage(sparkyTheme.background.keyName), context),
];
}
}

@ -27,13 +27,19 @@ class StartGameListener extends StatelessWidget {
case StartGameStatus.initial:
break;
case StartGameStatus.selectCharacter:
_onSelectCharacter(context);
_game.gameFlowController.start();
// We need to add a delay between starting the game and showing
// the dialog.
Future.delayed(
const Duration(milliseconds: 1300),
() => _onSelectCharacter(context),
);
break;
case StartGameStatus.howToPlay:
_onHowToPlay(context);
break;
case StartGameStatus.play:
_game.gameFlowController.start();
break;
}
},
@ -55,12 +61,7 @@ Future<void> _onHowToPlay(BuildContext context) async {
context: context,
child: HowToPlayDialog(
onDismissCallback: () {
// We need to add a delay between closing the dialog and starting the
// game.
Future.delayed(
kThemeAnimationDuration,
() => context.read<StartGameBloc>().add(const HowToPlayFinished()),
);
context.read<StartGameBloc>().add(const HowToPlayFinished());
},
),
);

@ -0,0 +1,86 @@
// 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:mockingjay/mockingjay.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_theme/pinball_theme.dart';
import '../helpers/helpers.dart';
void main() {
late CharacterThemeCubit characterThemeCubit;
late StartGameBloc startGameBloc;
setUp(() {
characterThemeCubit = MockCharacterThemeCubit();
startGameBloc = MockStartGameBloc();
whenListen(
characterThemeCubit,
const Stream<CharacterThemeState>.empty(),
initialState: const CharacterThemeState.initial(),
);
});
group('CharacterSelectionPage', () {
testWidgets('renders CharacterSelectionView', (tester) async {
await tester.pumpApp(
CharacterSelectionDialog(),
characterThemeCubit: characterThemeCubit,
);
expect(find.byType(CharacterSelectionView), findsOneWidget);
});
});
group('CharacterSelectionView', () {
testWidgets('renders correctly', (tester) async {
const titleText = 'Choose your character!';
await tester.pumpApp(
CharacterSelectionView(),
characterThemeCubit: characterThemeCubit,
);
expect(find.text(titleText), findsOneWidget);
expect(find.byType(SelectedCharacter), findsOneWidget);
expect(find.byType(TextButton), findsOneWidget);
});
testWidgets('calls characterSelected when a character image is tapped',
(tester) async {
const sparkyButtonKey = Key('characterSelectionPage_sparkyButton');
await tester.pumpApp(
CharacterSelectionView(),
characterThemeCubit: characterThemeCubit,
);
await tester.tap(find.byKey(sparkyButtonKey));
verify(() => characterThemeCubit.characterSelected(SparkyTheme()))
.called(1);
});
testWidgets('adds CharacterSelected event when start is tapped',
(tester) async {
whenListen(
startGameBloc,
Stream.value(const StartGameState.initial()),
initialState: const StartGameState.initial(),
);
await tester.pumpApp(
CharacterSelectionView(),
characterThemeCubit: characterThemeCubit,
startGameBloc: startGameBloc,
);
await tester.ensureVisible(find.byType(TextButton));
await tester.tap(find.byType(TextButton));
await tester.pumpAndSettle();
verify(() => startGameBloc.add(CharacterSelected())).called(1);
});
});
}
Loading…
Cancel
Save