From 45f1c6e48bf09ee8529ce762c3a70721abf9363a Mon Sep 17 00:00:00 2001 From: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> Date: Mon, 14 Mar 2022 15:27:03 -0500 Subject: [PATCH] feat: add how to play dialog (#45) * feat: add how to play dialog * style: comma for readability --- lib/l10n/arb/app_en.arb | 12 ++ lib/landing/view/landing_page.dart | 178 ++++++++++++++++++++++- test/landing/view/landing_page_test.dart | 58 ++++++-- 3 files changed, 234 insertions(+), 14 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index f12ccf7d..a118501e 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -4,6 +4,18 @@ "@play": { "description": "Text displayed on the landing page play button" }, + "howToPlay": "How to Play", + "@howToPlay": { + "description": "Text displayed on the landing page how to play button" + }, + "launchControls": "Launch Controls", + "@launchControls": { + "description": "Text displayed on the how to play dialog with the launch controls" + }, + "flipperControls": "Flipper Controls", + "@flipperControls": { + "description": "Text displayed on the how to play dialog with the flipper controls" + }, "start": "Start", "@start": { "description": "Text displayed on the character selection page start button" diff --git a/lib/landing/view/landing_page.dart b/lib/landing/view/landing_page.dart index 38951da6..5b0474b6 100644 --- a/lib/landing/view/landing_page.dart +++ b/lib/landing/view/landing_page.dart @@ -13,12 +13,182 @@ class LandingPage extends StatelessWidget { return Scaffold( body: Center( - child: TextButton( - onPressed: () => - Navigator.of(context).push(CharacterSelectionPage.route()), - child: Text(l10n.play), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + onPressed: () => Navigator.of(context).push( + CharacterSelectionPage.route(), + ), + child: Text(l10n.play), + ), + TextButton( + onPressed: () => showDialog( + context: context, + builder: (_) => const _HowToPlayDialog(), + ), + child: Text(l10n.howToPlay), + ), + ], ), ), ); } } + +class _HowToPlayDialog extends StatelessWidget { + const _HowToPlayDialog({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + const spacing = SizedBox(height: 16); + + return Dialog( + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(l10n.howToPlay), + spacing, + const _LaunchControls(), + spacing, + const _FlipperControls(), + ], + ), + ), + ); + } +} + +class _LaunchControls extends StatelessWidget { + const _LaunchControls({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + const spacing = SizedBox(width: 10); + + return Column( + children: [ + Text(l10n.launchControls), + const SizedBox(height: 10), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + KeyIndicator.fromIcon(keyIcon: Icons.keyboard_arrow_down), + spacing, + KeyIndicator.fromKeyName(keyName: 'SPACE'), + spacing, + KeyIndicator.fromKeyName(keyName: 'S'), + ], + ) + ], + ); + } +} + +class _FlipperControls extends StatelessWidget { + const _FlipperControls({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + const rowSpacing = SizedBox(width: 20); + + return Column( + children: [ + Text(l10n.flipperControls), + const SizedBox(height: 10), + Column( + children: [ + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + KeyIndicator.fromIcon(keyIcon: Icons.keyboard_arrow_left), + rowSpacing, + KeyIndicator.fromIcon(keyIcon: Icons.keyboard_arrow_right), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + KeyIndicator.fromKeyName(keyName: 'A'), + rowSpacing, + KeyIndicator.fromKeyName(keyName: 'D'), + ], + ) + ], + ) + ], + ); + } +} + +// TODO(allisonryan0002): remove visibility when adding final UI. +@visibleForTesting +class KeyIndicator extends StatelessWidget { + const KeyIndicator._({ + Key? key, + required String keyName, + required IconData keyIcon, + required bool fromIcon, + }) : _keyName = keyName, + _keyIcon = keyIcon, + _fromIcon = fromIcon, + super(key: key); + + const KeyIndicator.fromKeyName({Key? key, required String keyName}) + : this._( + key: key, + keyName: keyName, + keyIcon: Icons.keyboard_arrow_down, + fromIcon: false, + ); + + const KeyIndicator.fromIcon({Key? key, required IconData keyIcon}) + : this._( + key: key, + keyName: '', + keyIcon: keyIcon, + fromIcon: true, + ); + + final String _keyName; + + final IconData _keyIcon; + + final bool _fromIcon; + + @override + Widget build(BuildContext context) { + const iconPadding = EdgeInsets.all(15); + const textPadding = EdgeInsets.symmetric(vertical: 20, horizontal: 22); + final boarderColor = Colors.blue.withOpacity(0.5); + final color = Colors.blue.withOpacity(0.7); + + return DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: boarderColor, + width: 3, + ), + ), + child: _fromIcon + ? Padding( + padding: iconPadding, + child: Icon(_keyIcon, color: color), + ) + : Padding( + padding: textPadding, + child: Text(_keyName, style: TextStyle(color: color)), + ), + ); + } +} diff --git a/test/landing/view/landing_page_test.dart b/test/landing/view/landing_page_test.dart index ab036f9c..369f8cab 100644 --- a/test/landing/view/landing_page_test.dart +++ b/test/landing/view/landing_page_test.dart @@ -1,33 +1,71 @@ +// ignore_for_file: prefer_const_constructors + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockingjay/mockingjay.dart'; +import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/landing/landing.dart'; import '../../helpers/helpers.dart'; void main() { group('LandingPage', () { - testWidgets('renders TextButton', (tester) async { - await tester.pumpApp(const LandingPage()); - expect(find.byType(TextButton), findsOneWidget); + testWidgets('renders correctly', (tester) async { + final l10n = await AppLocalizations.delegate.load(Locale('en')); + await tester.pumpApp(LandingPage()); + + expect(find.byType(TextButton), findsNWidgets(2)); + expect(find.text(l10n.play), findsOneWidget); + expect(find.text(l10n.howToPlay), findsOneWidget); }); - testWidgets('tapping on TextButton navigates to CharacterSelectionPage', + testWidgets('tapping on play button navigates to CharacterSelectionPage', (tester) async { + final l10n = await AppLocalizations.delegate.load(Locale('en')); final navigator = MockNavigator(); when(() => navigator.push(any())).thenAnswer((_) async {}); await tester.pumpApp( - const LandingPage(), + LandingPage(), navigator: navigator, ); - await tester.tap( - find.byType( - TextButton, - ), - ); + + await tester.tap(find.widgetWithText(TextButton, l10n.play)); verify(() => navigator.push(any())).called(1); }); + + testWidgets('tapping on how to play button displays dialog with controls', + (tester) async { + final l10n = await AppLocalizations.delegate.load(Locale('en')); + await tester.pumpApp(LandingPage()); + + await tester.tap(find.widgetWithText(TextButton, l10n.howToPlay)); + await tester.pump(); + + expect(find.byType(Dialog), findsOneWidget); + }); + }); + + group('KeyIndicator', () { + testWidgets('fromKeyName renders correctly', (tester) async { + const keyName = 'A'; + + await tester.pumpApp( + KeyIndicator.fromKeyName(keyName: keyName), + ); + + expect(find.text(keyName), findsOneWidget); + }); + + testWidgets('fromIcon renders correctly', (tester) async { + const keyIcon = Icons.keyboard_arrow_down; + + await tester.pumpApp( + KeyIndicator.fromIcon(keyIcon: keyIcon), + ); + + expect(find.byIcon(keyIcon), findsOneWidget); + }); }); }