feat: added how to play dialog for desktop and mobile (#242)

* chore: create app_ui package

* chore: add dialog asset

* chore: create DialogDecoration

* chore: import app_ui

* feat: use DialogDecoration in app

* fix: update description

* fix: rename package

* fix: update README

* fix: exlude generated files from analysis

* fix: update workflow file

* fix: update tests

* fix: update description

* added how to play content

* added how to play screen for mobile devices (#245)

* ui: added how to play dialog for mobile

* chore: added mobile dialog content test

* fix: apply code review

* chore: addressed refactor feedback

* feat: how to play dialog is timed and manually dismissible (#251)

* chore: autoclose how to play dialog after 3 seconds

* test: added test for barrier dismiss on tap

* fix: update description

* fix: add Material to decoration

* chore: swapped asset for the spacebar

* chore: removed duplicate variable

* chore: moved timer logic to how to play dialog

* ui: added rocket to mobile launch directions

* feat(platform_helper): created a platform helper package to detect web vs mobile

* feat(platform_helper): import platform helper into pinball

* test: wrote tests for platform detection on how to play dialog

* chore: added platform helper package to ci

* chore: address foreseen nits

Co-authored-by: arturplaczek <arturp1989@gmail.com>
Co-authored-by: arturplaczek <33895544+arturplaczek@users.noreply.github.com>
pull/262/head
jonathandaniels-vgv 3 years ago committed by GitHub
parent afeb7884af
commit 21b54a0cdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,23 @@
name: platform_helper
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
paths:
- "packages/platform_helper/**"
- ".github/workflows/platform_helper.yaml"
pull_request:
paths:
- "packages/platform_helper/**"
- ".github/workflows/platform_helper.yaml"
jobs:
build:
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
with:
working_directory: packages/platform_helper
coverage_excludes: "lib/gen/*.dart"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -21,7 +21,7 @@ class RoundCountDisplay extends StatelessWidget {
Text(
l10n.rounds,
style: AppTextStyle.subtitle1.copyWith(
color: AppColors.orange,
color: AppColors.yellow,
),
),
const SizedBox(width: 8),
@ -53,7 +53,7 @@ class RoundIndicator extends StatelessWidget {
@override
Widget build(BuildContext context) {
final color = isActive ? AppColors.orange : AppColors.orange.withAlpha(128);
final color = isActive ? AppColors.yellow : AppColors.yellow.withAlpha(128);
const size = 8.0;
return Padding(

@ -59,7 +59,7 @@ class _ScoreDisplay extends StatelessWidget {
Text(
l10n.score.toLowerCase(),
style: AppTextStyle.subtitle1.copyWith(
color: AppColors.orange,
color: AppColors.yellow,
),
),
const _ScoreText(),

@ -47,6 +47,14 @@ class $AssetsImagesComponentsGen {
/// File path: assets/images/components/background.png
AssetGenImage get background =>
const AssetGenImage('assets/images/components/background.png');
/// File path: assets/images/components/key.png
AssetGenImage get key =>
const AssetGenImage('assets/images/components/key.png');
/// File path: assets/images/components/space.png
AssetGenImage get space =>
const AssetGenImage('assets/images/components/space.png');
}
class $AssetsImagesScoreGen {

@ -8,6 +8,10 @@
"@howToPlay": {
"description": "Text displayed on the landing page how to play button"
},
"tipsForFlips": "Tips for flips",
"@tipsForFlips": {
"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"
@ -16,6 +20,26 @@
"@flipperControls": {
"description": "Text displayed on the how to play dialog with the flipper controls"
},
"tapAndHoldRocket": "Tap & Hold Rocket",
"@tapAndHoldRocket": {
"description": "Text displayed on the how to launch on mobile"
},
"to": "to",
"@to": {
"description": "Text displayed for the word to"
},
"launch": "LAUNCH",
"@launch": {
"description": "Text displayed for the word launch"
},
"tapLeftRightScreen": "Tap left/right screen",
"@tapLeftRightScreen": {
"description": "Text displayed on the how to flip on mobile"
},
"flip": "FLIP",
"@flip": {
"description": "Text displayed for the word FLIP"
},
"start": "Start",
"@start": {
"description": "Text displayed on the character selection page start button"
@ -24,6 +48,10 @@
"@select": {
"description": "Text displayed on the character selection page select button"
},
"space": "Space",
"@space": {
"description": "Text displayed on space control button"
},
"characterSelectionTitle": "Choose your character!",
"@characterSelectionTitle": {
"description": "Title text displayed on the character selection page"
@ -80,4 +108,4 @@
"@rounds": {
"description": "Text displayed on the scoreboard widget to indicate rounds left"
}
}
}

@ -49,14 +49,13 @@ class CharacterSelectionView extends StatelessWidget {
Navigator.of(context).pop();
// TODO(arturplaczek): remove after merge StarBlocListener
final height = MediaQuery.of(context).size.height * 0.5;
showDialog<void>(
context: context,
builder: (_) => Center(
child: SizedBox(
height: height,
width: height * 1.4,
child: const HowToPlayDialog(),
child: HowToPlayDialog(),
),
),
);

@ -1,33 +1,236 @@
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:pinball/gen/gen.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/theme/theme.dart';
import 'package:pinball_ui/pinball_ui.dart';
import 'package:platform_helper/platform_helper.dart';
@visibleForTesting
enum Control {
left,
right,
down,
a,
d,
s,
space,
}
extension on Control {
bool get isArrow => isDown || isRight || isLeft;
bool get isDown => this == Control.down;
bool get isRight => this == Control.right;
bool get isLeft => this == Control.left;
bool get isSpace => this == Control.space;
String getCharacter(BuildContext context) {
switch (this) {
case Control.a:
return 'A';
case Control.d:
return 'D';
case Control.down:
return '>'; // Will be rotated
case Control.left:
return '<';
case Control.right:
return '>';
case Control.s:
return 'S';
case Control.space:
return context.l10n.space;
}
}
}
class HowToPlayDialog extends StatefulWidget {
HowToPlayDialog({
Key? key,
@visibleForTesting PlatformHelper? platformHelper,
}) : platformHelper = platformHelper ?? PlatformHelper(),
super(key: key);
final PlatformHelper platformHelper;
@override
State<HowToPlayDialog> createState() => _HowToPlayDialogState();
}
class _HowToPlayDialogState extends State<HowToPlayDialog> {
late Timer closeTimer;
@override
void initState() {
super.initState();
closeTimer = Timer(const Duration(seconds: 3), () {
if (mounted) {
Navigator.of(context).maybePop();
}
});
}
@override
void dispose() {
closeTimer.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isMobile = widget.platformHelper.isMobile;
return PixelatedDecoration(
header: const _HowToPlayHeader(),
body: isMobile ? const _MobileBody() : const _DesktopBody(),
);
}
}
class _MobileBody extends StatelessWidget {
const _MobileBody({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final paddingWidth = MediaQuery.of(context).size.width * 0.15;
final paddingHeight = MediaQuery.of(context).size.height * 0.075;
return FittedBox(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: paddingWidth,
),
child: Column(
children: [
const _MobileLaunchControls(),
SizedBox(height: paddingHeight),
const _MobileFlipperControls(),
],
),
),
);
}
}
class _MobileLaunchControls extends StatelessWidget {
const _MobileLaunchControls({Key? key}) : super(key: key);
class HowToPlayDialog extends StatelessWidget {
const HowToPlayDialog({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
const textStyle = AppTextStyle.subtitle3;
return Column(
children: [
Text(
l10n.tapAndHoldRocket,
style: textStyle,
),
Text.rich(
TextSpan(
children: [
TextSpan(
text: '${l10n.to} ',
style: textStyle,
),
TextSpan(
text: l10n.launch,
style: textStyle.copyWith(
color: AppColors.blue,
),
),
],
),
),
],
);
}
}
class _MobileFlipperControls extends StatelessWidget {
const _MobileFlipperControls({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
const textStyle = AppTextStyle.subtitle3;
return Column(
children: [
Text(
l10n.tapLeftRightScreen,
style: textStyle,
),
Text.rich(
TextSpan(
children: [
TextSpan(
text: '${l10n.to} ',
style: textStyle,
),
TextSpan(
text: l10n.flip,
style: textStyle.copyWith(
color: AppColors.orange,
),
),
],
),
),
],
);
}
}
class _DesktopBody extends StatelessWidget {
const _DesktopBody({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
const spacing = SizedBox(height: 16);
return ListView(
children: const [
spacing,
_DesktopLaunchControls(),
spacing,
_DesktopFlipperControls(),
],
);
}
}
return PixelatedDecoration(
header: Text(l10n.howToPlay),
body: ListView(
children: const [
spacing,
_LaunchControls(),
spacing,
_FlipperControls(),
class _HowToPlayHeader extends StatelessWidget {
const _HowToPlayHeader({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
const headerTextStyle = AppTextStyle.title;
return FittedBox(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
l10n.howToPlay,
style: headerTextStyle.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(
l10n.tipsForFlips,
style: headerTextStyle,
),
],
),
);
}
}
class _LaunchControls extends StatelessWidget {
const _LaunchControls({Key? key}) : super(key: key);
class _DesktopLaunchControls extends StatelessWidget {
const _DesktopLaunchControls({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -36,15 +239,18 @@ class _LaunchControls extends StatelessWidget {
return Column(
children: [
Text(l10n.launchControls),
Text(
l10n.launchControls,
style: AppTextStyle.headline4,
),
const SizedBox(height: 10),
Wrap(
children: const [
KeyIndicator.fromIcon(keyIcon: Icons.keyboard_arrow_down),
KeyButton(control: Control.down),
spacing,
KeyIndicator.fromKeyName(keyName: 'SPACE'),
KeyButton(control: Control.space),
spacing,
KeyIndicator.fromKeyName(keyName: 'S'),
KeyButton(control: Control.s),
],
)
],
@ -52,8 +258,8 @@ class _LaunchControls extends StatelessWidget {
}
}
class _FlipperControls extends StatelessWidget {
const _FlipperControls({Key? key}) : super(key: key);
class _DesktopFlipperControls extends StatelessWidget {
const _DesktopFlipperControls({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -62,7 +268,10 @@ class _FlipperControls extends StatelessWidget {
return Column(
children: [
Text(l10n.flipperControls),
Text(
l10n.flipperControls,
style: AppTextStyle.subtitle2,
),
const SizedBox(height: 10),
Column(
children: [
@ -70,17 +279,17 @@ class _FlipperControls extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: const [
KeyIndicator.fromIcon(keyIcon: Icons.keyboard_arrow_left),
KeyButton(control: Control.left),
rowSpacing,
KeyIndicator.fromIcon(keyIcon: Icons.keyboard_arrow_right),
KeyButton(control: Control.right),
],
),
const SizedBox(height: 8),
Wrap(
children: const [
KeyIndicator.fromKeyName(keyName: 'A'),
KeyButton(control: Control.a),
rowSpacing,
KeyIndicator.fromKeyName(keyName: 'D'),
KeyButton(control: Control.d),
],
)
],
@ -90,65 +299,46 @@ class _FlipperControls extends StatelessWidget {
}
}
// TODO(allisonryan0002): remove visibility when adding final UI.
@visibleForTesting
class KeyIndicator extends StatelessWidget {
const KeyIndicator._({
class KeyButton extends StatelessWidget {
const KeyButton({
Key? key,
required String keyName,
required IconData keyIcon,
required bool fromIcon,
}) : _keyName = keyName,
_keyIcon = keyIcon,
_fromIcon = fromIcon,
required Control control,
}) : _control = control,
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;
final Control _control;
@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);
final textStyle =
_control.isArrow ? AppTextStyle.headline1 : AppTextStyle.headline3;
const height = 60.0;
final width = _control.isSpace ? height * 2.83 : height;
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: boarderColor,
width: 3,
image: DecorationImage(
fit: BoxFit.fill,
image: AssetImage(
_control.isSpace
? Assets.images.components.space.keyName
: Assets.images.components.key.keyName,
),
),
),
child: _fromIcon
? Padding(
padding: iconPadding,
child: Icon(_keyIcon, color: color),
)
: Padding(
padding: textPadding,
child: Text(_keyName, style: TextStyle(color: color)),
child: SizedBox(
width: width,
height: height,
child: Center(
child: RotatedBox(
quarterTurns: _control.isDown ? 1 : 0,
child: Text(
_control.getCharacter(context),
style: textStyle.copyWith(color: AppColors.white),
),
),
),
),
);
}
}

@ -7,7 +7,9 @@ abstract class AppColors {
static const Color darkBlue = Color(0xFF0C32A4);
static const Color orange = Color(0xFFFFEE02);
static const Color yellow = Color(0xFFFFEE02);
static const Color orange = Color(0xFFE5AB05);
static const Color blue = Color(0xFF4B94F6);

@ -27,6 +27,35 @@ abstract class AppTextStyle {
fontFamily: _primaryFontFamily,
);
static const headline4 = TextStyle(
color: AppColors.white,
fontSize: 16,
package: _fontPackage,
fontFamily: _primaryFontFamily,
);
static const title = TextStyle(
color: AppColors.darkBlue,
fontSize: 20,
package: _fontPackage,
fontFamily: _primaryFontFamily,
);
static const subtitle3 = TextStyle(
color: AppColors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
package: _fontPackage,
fontFamily: _primaryFontFamily,
);
static const subtitle2 = TextStyle(
color: AppColors.white,
fontSize: 16,
package: _fontPackage,
fontFamily: _primaryFontFamily,
);
static const subtitle1 = TextStyle(
fontSize: 10,
fontFamily: _primaryFontFamily,

@ -0,0 +1,39 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# VSCode related
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json

@ -0,0 +1,11 @@
# platform_helper
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![License: MIT][license_badge]][license_link]
Platform helper for Pinball application.
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license_link]: https://opensource.org/licenses/MIT
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis

@ -0,0 +1 @@
include: package:very_good_analysis/analysis_options.2.4.0.yaml

@ -0,0 +1,3 @@
library platform_helper;
export 'src/platform_helper.dart';

@ -0,0 +1,12 @@
import 'package:flutter/foundation.dart';
/// {@template platform_helper}
/// Returns whether the current platform is running on a mobile device.
/// {@endtemplate}
class PlatformHelper {
/// {@macro platform_helper}
bool get isMobile {
return defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.android;
}
}

@ -0,0 +1,16 @@
name: platform_helper
description: Platform helper for Pinball application.
version: 1.0.0+1
publish_to: none
environment:
sdk: ">=2.16.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
very_good_analysis: ^2.4.0

@ -0,0 +1,39 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:platform_helper/platform_helper.dart';
void main() {
group('PlatformHelper', () {
test('can be instantiated', () {
expect(PlatformHelper(), isNotNull);
});
group('isMobile', () {
tearDown(() async {
debugDefaultTargetPlatformOverride = null;
});
test('returns true when defaultTargetPlatform is iOS', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
expect(PlatformHelper().isMobile, isTrue);
debugDefaultTargetPlatformOverride = null;
});
test('returns true when defaultTargetPlatform is android', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
expect(PlatformHelper().isMobile, isTrue);
debugDefaultTargetPlatformOverride = null;
});
test(
'returns false when defaultTargetPlatform is niether iOS nor android',
() async {
debugDefaultTargetPlatformOverride = TargetPlatform.macOS;
expect(PlatformHelper().isMobile, isFalse);
debugDefaultTargetPlatformOverride = null;
},
);
});
});
}

@ -513,6 +513,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
platform_helper:
dependency: "direct main"
description:
path: "packages/platform_helper"
relative: true
source: path
version: "1.0.0+1"
plugin_platform_interface:
dependency: transitive
description:

@ -37,6 +37,8 @@ dependencies:
path: packages/pinball_theme
pinball_ui:
path: packages/pinball_ui
platform_helper:
path: packages/platform_helper
dev_dependencies:
bloc_test: ^9.0.2

@ -108,7 +108,7 @@ void main() {
expect(
find.byWidgetPredicate(
(widget) => widget is Container && widget.color == AppColors.orange,
(widget) => widget is Container && widget.color == AppColors.yellow,
),
findsOneWidget,
);
@ -125,7 +125,7 @@ void main() {
find.byWidgetPredicate(
(widget) =>
widget is Container &&
widget.color == AppColors.orange.withAlpha(128),
widget.color == AppColors.yellow.withAlpha(128),
),
findsOneWidget,
);

@ -1,5 +1,7 @@
// ignore_for_file: prefer_const_constructors
import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
@ -84,17 +86,68 @@ void main() {
.called(1);
});
testWidgets('displays how to play dialog when start is tapped',
group('HowToPlayDialog', () {
testWidgets(
'is displayed for 3 seconds when start is tapped',
(tester) async {
await tester.pumpApp(
CharacterSelectionView(),
characterThemeCubit: characterThemeCubit,
await tester.pumpApp(
Scaffold(
body: Builder(
builder: (context) {
return ElevatedButton(
onPressed: () {
Navigator.of(context)
.push<void>(CharacterSelectionDialog.route());
},
child: Text('Tap me'),
);
},
),
),
characterThemeCubit: characterThemeCubit,
);
await tester.tap(find.text('Tap me'));
await tester.pumpAndSettle();
await tester.ensureVisible(find.byType(TextButton));
await tester.tap(find.byType(TextButton));
await tester.pumpAndSettle();
expect(find.byType(HowToPlayDialog), findsOneWidget);
await tester.pump(Duration(seconds: 3));
await tester.pumpAndSettle();
expect(find.byType(HowToPlayDialog), findsNothing);
},
);
await tester.ensureVisible(find.byType(TextButton));
await tester.tap(find.byType(TextButton));
await tester.pumpAndSettle();
expect(find.byType(HowToPlayDialog), findsOneWidget);
testWidgets(
'can be dismissed manually before 3 seconds have passed',
(tester) async {
await tester.pumpApp(
Scaffold(
body: Builder(
builder: (context) {
return ElevatedButton(
onPressed: () {
Navigator.of(context)
.push<void>(CharacterSelectionDialog.route());
},
child: Text('Tap me'),
);
},
),
),
characterThemeCubit: characterThemeCubit,
);
await tester.tap(find.text('Tap me'));
await tester.pumpAndSettle();
await tester.ensureVisible(find.byType(TextButton));
await tester.tap(find.byType(TextButton));
await tester.pumpAndSettle();
expect(find.byType(HowToPlayDialog), findsOneWidget);
await tester.tapAt(Offset(1, 1));
await tester.pumpAndSettle();
expect(find.byType(HowToPlayDialog), findsNothing);
},
);
});
});

@ -2,41 +2,68 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:platform_helper/platform_helper.dart';
import '../../helpers/helpers.dart';
class MockPlatformHelper extends Mock implements PlatformHelper {}
void main() {
group('HowToPlayDialog', () {
testWidgets('displays content', (tester) async {
final l10n = await AppLocalizations.delegate.load(Locale('en'));
await tester.pumpApp(HowToPlayDialog());
late AppLocalizations l10n;
late PlatformHelper platformHelper;
expect(find.text(l10n.launchControls), findsOneWidget);
setUp(() async {
l10n = await AppLocalizations.delegate.load(Locale('en'));
platformHelper = MockPlatformHelper();
});
});
group('KeyIndicator', () {
testWidgets('fromKeyName renders correctly', (tester) async {
const keyName = 'A';
testWidgets(
'can be instantiated without passing in a platform helper',
(tester) async {
await tester.pumpApp(HowToPlayDialog());
expect(find.byType(HowToPlayDialog), findsOneWidget);
},
);
testWidgets('displays content for desktop', (tester) async {
when(() => platformHelper.isMobile).thenAnswer((_) => false);
await tester.pumpApp(
KeyIndicator.fromKeyName(keyName: keyName),
HowToPlayDialog(
platformHelper: platformHelper,
),
);
expect(find.text(keyName), findsOneWidget);
expect(find.text(l10n.howToPlay), findsOneWidget);
expect(find.text(l10n.tipsForFlips), findsOneWidget);
expect(find.text(l10n.launchControls), findsOneWidget);
expect(find.text(l10n.flipperControls), findsOneWidget);
expect(find.byType(KeyButton), findsNWidgets(7));
});
testWidgets('fromIcon renders correctly', (tester) async {
const keyIcon = Icons.keyboard_arrow_down;
testWidgets('displays content for mobile', (tester) async {
when(() => platformHelper.isMobile).thenAnswer((_) => true);
await tester.pumpApp(
HowToPlayDialog(
platformHelper: platformHelper,
),
);
expect(find.text(l10n.howToPlay), findsOneWidget);
expect(find.text(l10n.tipsForFlips), findsOneWidget);
expect(find.text(l10n.tapAndHoldRocket), findsOneWidget);
expect(find.text(l10n.tapLeftRightScreen), findsOneWidget);
});
});
group('KeyButton', () {
testWidgets('renders correctly', (tester) async {
await tester.pumpApp(
KeyIndicator.fromIcon(keyIcon: keyIcon),
KeyButton(control: Control.a),
);
expect(find.byIcon(keyIcon), findsOneWidget);
expect(find.text('A'), findsOneWidget);
});
});
}

Loading…
Cancel
Save