Merge branch 'main' into feat/bumper-sfx

pull/315/head
Erick 3 years ago committed by GitHub
commit 96786c4474
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,8 +1 @@
// Copyright (c) 2021, Very Good Ventures
// https://verygood.ventures
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
export 'view/app.dart';

@ -1,10 +1,3 @@
// Copyright (c) 2021, Very Good Ventures
// https://verygood.ventures
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
// ignore_for_file: public_member_api_docs
import 'package:authentication_repository/authentication_repository.dart';

@ -0,0 +1,2 @@
export 'cubit/assets_manager_cubit.dart';
export 'views/views.dart';

@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/assets_manager/assets_manager.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_ui/pinball_ui.dart';
/// {@template assets_loading_page}
/// Widget used to indicate the loading progress of the different assets used
/// in the game
/// {@endtemplate}
class AssetsLoadingPage extends StatelessWidget {
/// {@macro assets_loading_page}
const AssetsLoadingPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final headline1 = Theme.of(context).textTheme.headline1;
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
l10n.ioPinball,
style: headline1!.copyWith(fontSize: 80),
textAlign: TextAlign.center,
),
const SizedBox(height: 40),
AnimatedEllipsisText(
l10n.loading,
style: headline1,
),
const SizedBox(height: 40),
FractionallySizedBox(
widthFactor: 0.8,
child: BlocBuilder<AssetsManagerCubit, AssetsManagerState>(
builder: (context, state) {
return PinballLoadingIndicator(value: state.progress);
},
),
),
],
),
);
}
}

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

@ -1,10 +1,3 @@
// Copyright (c) 2021, Very Good Ventures
// https://verygood.ventures
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
// ignore_for_file: public_member_api_docs
import 'dart:async';

@ -1,4 +1,3 @@
export 'assets_manager/cubit/assets_manager_cubit.dart';
export 'bloc/game_bloc.dart';
export 'components/components.dart';
export 'game_assets.dart';

@ -4,10 +4,12 @@ import 'package:flame/game.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/assets_manager/assets_manager.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_ui/pinball_ui.dart';
class PinballGamePage extends StatelessWidget {
const PinballGamePage({
@ -71,32 +73,13 @@ class PinballGameView extends StatelessWidget {
final isLoading = context.select(
(AssetsManagerCubit bloc) => bloc.state.progress != 1,
);
return Scaffold(
backgroundColor: Colors.blue,
return Container(
decoration: const CrtBackground(),
child: Scaffold(
backgroundColor: PinballColors.transparent,
body: isLoading
? const _PinballGameLoadingView()
? const AssetsLoadingPage()
: PinballGameLoadedView(game: game),
);
}
}
class _PinballGameLoadingView extends StatelessWidget {
const _PinballGameLoadingView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final loadingProgress = context.select(
(AssetsManagerCubit bloc) => bloc.state.progress,
);
return Padding(
padding: const EdgeInsets.all(24),
child: Center(
child: LinearProgressIndicator(
color: Colors.white,
value: loadingProgress,
),
),
);
}

@ -3,8 +3,10 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/gen/gen.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_ui/pinball_ui.dart';
import 'package:platform_helper/platform_helper.dart';
@ -50,10 +52,13 @@ extension on Control {
}
Future<void> showHowToPlayDialog(BuildContext context) {
final audio = context.read<PinballAudio>();
return showDialog<void>(
context: context,
builder: (_) => HowToPlayDialog(),
);
).then((_) {
audio.ioPinballVoiceOver();
});
}
class HowToPlayDialog extends StatefulWidget {

@ -20,7 +20,7 @@
"@flipperControls": {
"description": "Text displayed on the how to play dialog with the flipper controls"
},
"tapAndHoldRocket": "Tap & Hold Rocket",
"tapAndHoldRocket": "Tap Rocket",
"@tapAndHoldRocket": {
"description": "Text displayed on the how to launch on mobile"
},
@ -123,5 +123,13 @@
"footerGoogleIOText": "Google I/O",
"@footerGoogleIOText": {
"description": "Text shown on the footer which mentions Google I/O"
},
"loading": "Loading",
"@loading": {
"description": "Text shown to indicate loading times"
},
"ioPinball": "I/O Pinball",
"@ioPinball": {
"description": "I/O Pinball - Name of the game"
}
}

@ -1,10 +1,3 @@
// Copyright (c) 2021, Very Good Ventures
// https://verygood.ventures
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
// ignore_for_file: public_member_api_docs
import 'package:flutter/widgets.dart';

@ -1,10 +1,3 @@
// Copyright (c) 2021, Very Good Ventures
// https://verygood.ventures
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:async';
import 'package:authentication_repository/authentication_repository.dart';

@ -1,10 +1,3 @@
// Copyright (c) 2021, Very Good Ventures
// https://verygood.ventures
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:async';
import 'package:authentication_repository/authentication_repository.dart';

@ -1,10 +1,3 @@
// Copyright (c) 2021, Very Good Ventures
// https://verygood.ventures
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:async';
import 'package:authentication_repository/authentication_repository.dart';

@ -17,6 +17,7 @@ class $AssetsSfxGen {
String get bumperA => 'assets/sfx/bumper_a.mp3';
String get bumperB => 'assets/sfx/bumper_b.mp3';
String get google => 'assets/sfx/google.mp3';
String get ioPinballVoiceOver => 'assets/sfx/io_pinball_voice_over.mp3';
}
class Assets {

@ -88,6 +88,7 @@ class PinballAudio {
await Future.wait([
_preCacheSingleAudio(_prefixFile(Assets.sfx.google)),
_preCacheSingleAudio(_prefixFile(Assets.sfx.ioPinballVoiceOver)),
_preCacheSingleAudio(_prefixFile(Assets.music.background)),
]);
}
@ -102,6 +103,11 @@ class PinballAudio {
_playSingleAudio(_prefixFile(Assets.sfx.google));
}
/// Plays the I/O Pinball voice over audio.
void ioPinballVoiceOver() {
_playSingleAudio(_prefixFile(Assets.sfx.ioPinballVoiceOver));
}
/// Plays the background music
void backgroundMusic() {
_loopSingleAudio(_prefixFile(Assets.music.background));

@ -141,6 +141,11 @@ void main() {
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/google.mp3'),
).called(1);
verify(
() => preCacheSingleAudio.onCall(
'packages/pinball_audio/assets/sfx/io_pinball_voice_over.mp3',
),
).called(1);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/music/background.mp3'),
@ -209,6 +214,19 @@ void main() {
});
});
group('ioPinballVoiceOver', () {
test('plays the correct file', () async {
await audio.load();
audio.ioPinballVoiceOver();
verify(
() => playSingleAudio.onCall(
'packages/pinball_audio/${Assets.sfx.ioPinballVoiceOver}',
),
).called(1);
});
});
group('backgroundMusic', () {
test('plays the correct file', () async {
await audio.load();

@ -1,11 +1,11 @@
import 'dart:async';
import 'dart:math' as math;
import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/widgets.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/ball/behaviors/ball_scaling_behavior.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template ball}
@ -20,6 +20,7 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
renderBody: false,
children: [
_BallSpriteComponent()..tint(baseColor.withOpacity(0.5)),
BallScalingBehavior(),
],
) {
// TODO(ruimiguel): while developing Ball can be launched by clicking mouse,
@ -30,6 +31,15 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
layer = Layer.board;
}
/// Creates a [Ball] without any behaviors.
///
/// This can be used for testing [Ball]'s behaviors in isolation.
@visibleForTesting
Ball.test({required this.baseColor})
: super(
children: [_BallSpriteComponent()],
);
/// The size of the [Ball].
static final Vector2 size = Vector2.all(4.13);
@ -81,26 +91,9 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
void update(double dt) {
super.update(dt);
_rescaleSize();
_setPositionalGravity();
}
void _rescaleSize() {
final boardHeight = BoardDimensions.bounds.height;
const maxShrinkValue = BoardDimensions.perspectiveShrinkFactor;
final standardizedYPosition = body.position.y + (boardHeight / 2);
final scaleFactor = maxShrinkValue +
((standardizedYPosition / boardHeight) * (1 - maxShrinkValue));
body.fixtures.first.shape.radius = (size.x / 2) * scaleFactor;
// TODO(alestiago): Revisit and see if there's a better way to do this.
final spriteComponent = firstChild<_BallSpriteComponent>();
spriteComponent?.scale = Vector2.all(scaleFactor);
}
void _setPositionalGravity() {
final defaultGravity = gameRef.world.gravity.y;
final maxXDeviationFromCenter = BoardDimensions.bounds.width / 2;

@ -0,0 +1,24 @@
import 'package:flame/components.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// Scales the ball's body and sprite according to its position on the board.
class BallScalingBehavior extends Component with ParentIsA<Ball> {
@override
void update(double dt) {
super.update(dt);
final boardHeight = BoardDimensions.bounds.height;
const maxShrinkValue = BoardDimensions.perspectiveShrinkFactor;
final standardizedYPosition = parent.body.position.y + (boardHeight / 2);
final scaleFactor = maxShrinkValue +
((standardizedYPosition / boardHeight) * (1 - maxShrinkValue));
parent.body.fixtures.first.shape.radius = (Ball.size.x / 2) * scaleFactor;
parent.firstChild<SpriteComponent>()!.scale.setValues(
scaleFactor,
scaleFactor,
);
}
}

@ -2,7 +2,7 @@ export 'android_animatronic.dart';
export 'android_bumper/android_bumper.dart';
export 'android_spaceship/android_spaceship.dart';
export 'backboard/backboard.dart';
export 'ball.dart';
export 'ball/ball.dart';
export 'baseboard.dart';
export 'board_background_sprite_component.dart';
export 'board_dimensions.dart';

@ -1,9 +1,3 @@
// Copyright (c) 2022, Very Good Ventures
// https://verygood.ventures
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:dashbook/dashbook.dart';
import 'package:flutter/material.dart';
import 'package:sandbox/stories/stories.dart';

@ -6,18 +6,29 @@ import 'package:flame_test/flame_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/ball/behaviors/behaviors.dart';
import '../../helpers/helpers.dart';
import '../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
group('Ball', () {
const baseColor = Color(0xFFFFFFFF);
test(
'can be instantiated',
() {
expect(Ball(baseColor: baseColor), isA<Ball>());
expect(Ball.test(baseColor: baseColor), isA<Ball>());
},
);
flameTester.test(
'loads correctly',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ready();
await game.ensureAdd(ball);
@ -25,11 +36,20 @@ void main() {
},
);
flameTester.test('add a BallScalingBehavior', (game) async {
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
expect(
ball.descendants().whereType<BallScalingBehavior>().length,
equals(1),
);
});
group('body', () {
flameTester.test(
'is dynamic',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
expect(ball.body.bodyType, equals(BodyType.dynamic));
@ -38,7 +58,7 @@ void main() {
group('can be moved', () {
flameTester.test('by its weight', (game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
game.update(1);
@ -46,7 +66,7 @@ void main() {
});
flameTester.test('by applying velocity', (game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
ball.body.gravityScale = Vector2.zero();
@ -61,7 +81,7 @@ void main() {
flameTester.test(
'exists',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
expect(ball.body.fixtures[0], isA<Fixture>());
@ -71,7 +91,7 @@ void main() {
flameTester.test(
'is dense',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
final fixture = ball.body.fixtures[0];
@ -82,7 +102,7 @@ void main() {
flameTester.test(
'shape is circular',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
final fixture = ball.body.fixtures[0];
@ -94,7 +114,7 @@ void main() {
flameTester.test(
'has Layer.all as default filter maskBits',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ready();
await game.ensureAdd(ball);
await game.ready();
@ -108,7 +128,7 @@ void main() {
group('stop', () {
group("can't be moved", () {
flameTester.test('by its weight', (game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
ball.stop();
@ -116,19 +136,6 @@ void main() {
expect(ball.body.position, equals(ball.initialPosition));
});
});
// TODO(allisonryan0002): delete or retest this if/when solution is added
// to prevent forces on a ball while stopped.
// flameTester.test('by applying velocity', (game) async {
// final ball = Ball(baseColor: Colors.blue);
// await game.ensureAdd(ball);
// ball.stop();
// ball.body.linearVelocity.setValues(10, 10);
// game.update(1);
// expect(ball.body.position, equals(ball.initialPosition));
// });
});
group('resume', () {
@ -136,7 +143,7 @@ void main() {
flameTester.test(
'by its weight when previously stopped',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
ball.stop();
ball.resume();
@ -149,7 +156,7 @@ void main() {
flameTester.test(
'by applying velocity when previously stopped',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
ball.stop();
ball.resume();
@ -165,7 +172,7 @@ void main() {
group('boost', () {
flameTester.test('applies an impulse to the ball', (game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
expect(ball.body.linearVelocity, equals(Vector2.zero()));
@ -176,7 +183,7 @@ void main() {
});
flameTester.test('adds TurboChargeSpriteAnimation', (game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
await ball.boost(Vector2.all(10));
@ -190,7 +197,7 @@ void main() {
flameTester.test('removes TurboChargeSpriteAnimation after it finishes',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
await ball.boost(Vector2.all(10));

@ -0,0 +1,99 @@
// ignore_for_file: cascade_invocations
import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/ball/behaviors/behaviors.dart';
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final asset = Assets.images.ball.ball.keyName;
final flameTester = FlameTester(() => TestGame([asset]));
group('BallScalingBehavior', () {
const baseColor = Color(0xFFFFFFFF);
test('can be instantiated', () {
expect(
BallScalingBehavior(),
isA<BallScalingBehavior>(),
);
});
flameTester.test('can be loaded', (game) async {
final ball = Ball.test(baseColor: baseColor);
final behavior = BallScalingBehavior();
await ball.add(behavior);
await game.ensureAdd(ball);
expect(
ball.firstChild<BallScalingBehavior>(),
equals(behavior),
);
});
flameTester.test('can be loaded', (game) async {
final ball = Ball.test(baseColor: baseColor);
final behavior = BallScalingBehavior();
await ball.add(behavior);
await game.ensureAdd(ball);
expect(
ball.firstChild<BallScalingBehavior>(),
equals(behavior),
);
});
flameTester.test('scales the shape radius', (game) async {
final ball1 = Ball.test(baseColor: baseColor)
..initialPosition = Vector2(0, 10);
await ball1.add(BallScalingBehavior());
final ball2 = Ball.test(baseColor: baseColor)
..initialPosition = Vector2(0, -10);
await ball2.add(BallScalingBehavior());
await game.ensureAddAll([ball1, ball2]);
game.update(1);
final shape1 = ball1.body.fixtures.first.shape;
final shape2 = ball2.body.fixtures.first.shape;
expect(
shape1.radius,
greaterThan(shape2.radius),
);
});
flameTester.testGameWidget(
'scales the sprite',
setUp: (game, tester) async {
final ball1 = Ball.test(baseColor: baseColor)
..initialPosition = Vector2(0, 10);
await ball1.add(BallScalingBehavior());
final ball2 = Ball.test(baseColor: baseColor)
..initialPosition = Vector2(0, -10);
await ball2.add(BallScalingBehavior());
await game.ensureAddAll([ball1, ball2]);
game.update(1);
await tester.pump();
await game.ready();
final sprite1 = ball1.firstChild<SpriteComponent>()!;
final sprite2 = ball2.firstChild<SpriteComponent>()!;
expect(
sprite1.scale.x,
greaterThan(sprite2.scale.x),
);
expect(
sprite1.scale.y,
greaterThan(sprite2.scale.y),
);
},
);
});
}

@ -8,4 +8,9 @@ abstract class PinballColors {
static const Color orange = Color(0xFFE5AB05);
static const Color blue = Color(0xFF4B94F6);
static const Color transparent = Color(0x00000000);
static const Color loadingDarkRed = Color(0xFFE33B2D);
static const Color loadingLightRed = Color(0xFFEC5E2B);
static const Color loadingDarkBlue = Color(0xFF4087F8);
static const Color loadingLightBlue = Color(0xFF6CCAE4);
static const Color crtBackground = Color(0xFF274E54);
}

@ -0,0 +1,61 @@
import 'dart:async';
import 'package:flutter/material.dart';
/// {@tempalte animated_ellipsis_text}
/// Every 500 milliseconds, it will add a new `.` at the end of the given
/// [text]. Once 3 `.` have been added (e.g. `Loading...`), it will reset to
/// zero ellipsis and start over again.
/// {@endtemplate}
class AnimatedEllipsisText extends StatefulWidget {
/// {@macro animated_ellipsis_text}
const AnimatedEllipsisText(
this.text, {
Key? key,
this.style,
}) : super(key: key);
/// The text that will be animated.
final String text;
/// Optional [TextStyle] of the given [text].
final TextStyle? style;
@override
State<StatefulWidget> createState() => _AnimatedEllipsisText();
}
class _AnimatedEllipsisText extends State<AnimatedEllipsisText>
with SingleTickerProviderStateMixin {
late final Timer timer;
var _numberOfEllipsis = 0;
@override
void initState() {
super.initState();
timer = Timer.periodic(const Duration(milliseconds: 500), (_) {
setState(() {
_numberOfEllipsis++;
_numberOfEllipsis = _numberOfEllipsis % 4;
});
});
}
@override
void dispose() {
if (timer.isActive) timer.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text(
'${widget.text}${_numberOfEllipsis.toEllipsis()}',
style: widget.style,
);
}
}
extension on int {
String toEllipsis() => '.' * this;
}

@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:pinball_ui/pinball_ui.dart';
/// {@template crt_background}
/// [BoxDecoration] that provides a CRT-like background efffect.
/// {@endtemplate}
class CrtBackground extends BoxDecoration {
/// {@macro crt_background}
const CrtBackground()
: super(
gradient: const LinearGradient(
begin: Alignment(1, 0.015),
stops: [0.0, 0.5, 0.5, 1],
colors: [
PinballColors.darkBlue,
PinballColors.darkBlue,
PinballColors.crtBackground,
PinballColors.crtBackground,
],
tileMode: TileMode.repeated,
),
);
}

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:pinball_ui/pinball_ui.dart';
/// {@template pinball_loading_indicator}
/// Pixel-art loading indicator
/// {@endtemplate}
class PinballLoadingIndicator extends StatelessWidget {
/// {@macro pinball_loading_indicator}
const PinballLoadingIndicator({
Key? key,
required this.value,
}) : assert(
value >= 0.0 && value <= 1.0,
'Progress must be between 0 and 1',
),
super(key: key);
/// Progress value
final double value;
@override
Widget build(BuildContext context) {
return Column(
children: [
_InnerIndicator(value: value, widthFactor: 0.95),
_InnerIndicator(value: value, widthFactor: 0.98),
_InnerIndicator(value: value),
_InnerIndicator(value: value),
_InnerIndicator(value: value, widthFactor: 0.98),
_InnerIndicator(value: value, widthFactor: 0.95)
],
);
}
}
class _InnerIndicator extends StatelessWidget {
const _InnerIndicator({
Key? key,
required this.value,
this.widthFactor = 1.0,
}) : super(key: key);
final double value;
final double widthFactor;
@override
Widget build(BuildContext context) {
return FractionallySizedBox(
widthFactor: widthFactor,
child: Column(
children: [
LinearProgressIndicator(
backgroundColor: PinballColors.loadingDarkBlue,
color: PinballColors.loadingDarkRed,
value: value,
),
LinearProgressIndicator(
backgroundColor: PinballColors.loadingLightBlue,
color: PinballColors.loadingLightRed,
value: value,
),
],
),
);
}
}

@ -1 +1,4 @@
export 'animated_ellipsis_text.dart';
export 'crt_background.dart';
export 'pinball_button.dart';
export 'pinball_loading_indicator.dart';

@ -27,5 +27,25 @@ void main() {
test('transparent is 0x00000000', () {
expect(PinballColors.transparent, const Color(0x00000000));
});
test('loadingDarkRed is 0xFFE33B2D', () {
expect(PinballColors.loadingDarkRed, const Color(0xFFE33B2D));
});
test('loadingLightRed is 0xFFEC5E2B', () {
expect(PinballColors.loadingLightRed, const Color(0xFFEC5E2B));
});
test('loadingDarkBlue is 0xFF4087F8', () {
expect(PinballColors.loadingDarkBlue, const Color(0xFF4087F8));
});
test('loadingLightBlue is 0xFF6CCAE4', () {
expect(PinballColors.loadingLightBlue, const Color(0xFF6CCAE4));
});
test('crtBackground is 0xFF274E54', () {
expect(PinballColors.crtBackground, const Color(0xFF274E54));
});
});
}

@ -0,0 +1,30 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_ui/pinball_ui.dart';
void main() {
group('AnimatedEllipsisText', () {
testWidgets(
'adds a new `.` every 500ms and '
'resets back to zero after adding 3', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: AnimatedEllipsisText('test'),
),
),
);
expect(find.text('test'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 600));
expect(find.text('test.'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 600));
expect(find.text('test..'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 600));
expect(find.text('test...'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 600));
expect(find.text('test'), findsOneWidget);
});
});
}

@ -0,0 +1,25 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_ui/pinball_ui.dart';
void main() {
group('CrtBackground', () {
test('is a BoxDecoration with a LinearGradient', () {
// ignore: prefer_const_constructors
final crtBg = CrtBackground();
const expectedGradient = LinearGradient(
begin: Alignment(1, 0.015),
stops: [0.0, 0.5, 0.5, 1],
colors: [
PinballColors.darkBlue,
PinballColors.darkBlue,
PinballColors.crtBackground,
PinballColors.crtBackground,
],
tileMode: TileMode.repeated,
);
expect(crtBg, isA<BoxDecoration>());
expect(crtBg.gradient, expectedGradient);
});
});
}

@ -0,0 +1,45 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_ui/pinball_ui.dart';
void main() {
group('PinballLoadingIndicator', () {
group('assert value', () {
test('throws error if value <= 0.0', () {
expect(
() => PinballLoadingIndicator(value: -0.5),
throwsA(isA<AssertionError>()),
);
});
test('throws error if value >= 1.0', () {
expect(
() => PinballLoadingIndicator(value: 1.5),
throwsA(isA<AssertionError>()),
);
});
});
testWidgets(
'renders 12 LinearProgressIndicators and '
'6 FractionallySizedBox to indicate progress', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: PinballLoadingIndicator(value: 0.75),
),
),
);
expect(find.byType(FractionallySizedBox), findsNWidgets(6));
expect(find.byType(LinearProgressIndicator), findsNWidgets(12));
final progressIndicators = tester.widgetList<LinearProgressIndicator>(
find.byType(LinearProgressIndicator),
);
for (final i in progressIndicators) {
expect(i.value, 0.75);
}
});
});
}

@ -1,10 +1,3 @@
// Copyright (c) 2021, Very Good Ventures
// https://verygood.ventures
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:authentication_repository/authentication_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/assets_manager/assets_manager.dart';
void main() {
group('AssetsManagerCubit', () {

@ -1,7 +1,7 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/assets_manager/assets_manager.dart';
void main() {
group('AssetsManagerState', () {

@ -0,0 +1,38 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/assets_manager/assets_manager.dart';
import 'package:pinball_ui/pinball_ui.dart';
import '../../helpers/helpers.dart';
class _MockAssetsManagerCubit extends Mock implements AssetsManagerCubit {}
void main() {
late AssetsManagerCubit assetsManagerCubit;
setUp(() {
final initialAssetsState = AssetsManagerState(
loadables: [Future<void>.value()],
loaded: const [],
);
assetsManagerCubit = _MockAssetsManagerCubit();
whenListen(
assetsManagerCubit,
Stream.value(initialAssetsState),
initialState: initialAssetsState,
);
});
group('AssetsLoadingPage', () {
testWidgets('renders an animated text and a pinball loading indicator',
(tester) async {
await tester.pumpApp(
const AssetsLoadingPage(),
assetsManagerCubit: assetsManagerCubit,
);
expect(find.byType(AnimatedEllipsisText), findsOneWidget);
expect(find.byType(PinballLoadingIndicator), findsOneWidget);
});
});
}

@ -5,6 +5,7 @@ import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/assets_manager/assets_manager.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
@ -66,7 +67,6 @@ void main() {
Stream.value(initialAssetsState),
initialState: initialAssetsState,
);
await tester.pumpApp(
PinballGameView(
game: game,
@ -74,14 +74,7 @@ void main() {
assetsManagerCubit: assetsManagerCubit,
characterThemeCubit: characterThemeCubit,
);
expect(
find.byWidgetPredicate(
(widget) =>
widget is LinearProgressIndicator && widget.value == 0.0,
),
findsOneWidget,
);
expect(find.byType(AssetsLoadingPage), findsOneWidget);
},
);

@ -1,9 +1,3 @@
//
// Copyright (c) 2021, Very Good Ventures
// Use of this source code is governed by an MIT-style
// https://opensource.org/licenses/MIT.
// https://verygood.ventures
// license that can be found in the LICENSE file or at
export 'builders.dart';
export 'fakes.dart';
export 'forge2d.dart';

@ -1,10 +1,3 @@
// Copyright (c) 2021, Very Good Ventures
// https://verygood.ventures
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -12,6 +5,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
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/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart';

@ -3,10 +3,13 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/how_to_play/how_to_play.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:platform_helper/platform_helper.dart';
import '../helpers/helpers.dart';
class _MockPinballAudio extends Mock implements PinballAudio {}
class _MockPlatformHelper extends Mock implements PlatformHelper {}
void main() {
@ -93,5 +96,30 @@ void main() {
await tester.pumpAndSettle();
expect(find.byType(HowToPlayDialog), findsNothing);
});
testWidgets(
'plays the I/O Pinball voice over audio on dismiss',
(tester) async {
final audio = _MockPinballAudio();
await tester.pumpApp(
Builder(
builder: (context) {
return TextButton(
onPressed: () => showHowToPlayDialog(context),
child: const Text('test'),
);
},
),
pinballAudio: audio,
);
expect(find.byType(HowToPlayDialog), findsNothing);
await tester.tap(find.text('test'));
await tester.pumpAndSettle();
await tester.tapAt(Offset.zero);
await tester.pumpAndSettle();
verify(audio.ioPinballVoiceOver).called(1);
},
);
});
}

Loading…
Cancel
Save