Merge branch 'main' into refactor/use-descendants-bonus-word-test

pull/94/head
Alejandro Santiago 4 years ago committed by GitHub
commit d1af7074c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -16,4 +16,4 @@ jobs:
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
with:
working_directory: packages/pinball_components
coverage_excludes: "lib/src/generated/*.dart"
coverage_excludes: "lib/gen/*.dart"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

@ -12,19 +12,19 @@ const _attachedErrorMessage = "Can't add to attached Blueprints";
/// A [Blueprint] is a virtual way of grouping [Component]s
/// that are related, but they need to be added directly on
/// the [FlameGame] level.
abstract class Blueprint {
abstract class Blueprint<T extends FlameGame> {
final List<Component> _components = [];
bool _isAttached = false;
/// Called before the the [Component]s managed
/// by this blueprint is added to the [FlameGame]
void build();
void build(T gameRef);
/// Attach the [Component]s built on [build] to the [game]
/// instance
@mustCallSuper
Future<void> attach(FlameGame game) async {
build();
Future<void> attach(T game) async {
build(game);
await game.addAll(_components);
_isAttached = true;
}
@ -47,7 +47,7 @@ abstract class Blueprint {
/// A [Blueprint] that provides additional
/// structures specific to flame_forge2d
abstract class Forge2DBlueprint extends Blueprint {
abstract class Forge2DBlueprint extends Blueprint<Forge2DGame> {
final List<ContactCallback> _callbacks = [];
/// Adds a single [ContactCallback] to this blueprint
@ -63,13 +63,11 @@ abstract class Forge2DBlueprint extends Blueprint {
}
@override
Future<void> attach(FlameGame game) async {
Future<void> attach(Forge2DGame game) async {
await super.attach(game);
assert(game is Forge2DGame, 'Forge2DBlueprint used outside a Forge2DGame');
for (final callback in _callbacks) {
(game as Forge2DGame).addContactCallback(callback);
game.addContactCallback(callback);
}
}

@ -1,61 +1,40 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/flame/blueprint.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template ball}
/// A solid, [BodyType.dynamic] sphere that rolls and bounces along the
/// [PinballGame].
/// {@template ball_blueprint}
/// [Blueprint] which cretes a ball game object
/// {@endtemplate}
class Ball extends BodyComponent<PinballGame> with InitialPosition, Layered {
/// {@macro ball}
Ball() {
// TODO(ruimiguel): while developing Ball can be launched by clicking mouse,
// and default layer is Layer.all. But on final game Ball will be always be
// be launched from Plunger and LauncherRamp will modify it to Layer.board.
// We need to see what happens if Ball appears from other place like nest
// bumper, it will need to explicit change layer to Layer.board then.
layer = Layer.board;
}
/// The size of the [Ball]
final Vector2 size = Vector2.all(2);
class BallBlueprint extends Blueprint<PinballGame> {
/// {@macro ball_blueprint}
BallBlueprint({required this.position});
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(Assets.images.components.ball.path);
final tint = gameRef.theme.characterTheme.ballColor.withOpacity(0.5);
await add(
SpriteComponent(
sprite: sprite,
size: size,
anchor: Anchor.center,
)..tint(tint),
);
}
/// The initial position of the [Ball]
final Vector2 position;
@override
Body createBody() {
final shape = CircleShape()..radius = size.x / 2;
final fixtureDef = FixtureDef(shape)..density = 1;
void build(PinballGame gameRef) {
final baseColor = gameRef.theme.characterTheme.ballColor;
final ball = Ball(baseColor: baseColor)..add(BallController());
final bodyDef = BodyDef()
..position = initialPosition
..userData = this
..type = BodyType.dynamic;
return world.createBody(bodyDef)..createFixture(fixtureDef);
add(ball..initialPosition = position + Vector2(0, ball.size.y / 2));
}
}
/// {@template ball}
/// A solid, [BodyType.dynamic] sphere that rolls and bounces along the
/// [PinballGame].
/// {@endtemplate}
class BallController extends Component with HasGameRef<PinballGame> {
/// Removes the [Ball] from a [PinballGame]; spawning a new [Ball] if
/// any are left.
///
/// Triggered by [BottomWallBallContactCallback] when the [Ball] falls into
/// a [BottomWall].
void lost() {
shouldRemove = true;
parent?.shouldRemove = true;
final bloc = gameRef.read<GameBloc>()..add(const BallLost());
@ -64,19 +43,13 @@ class Ball extends BodyComponent<PinballGame> with InitialPosition, Layered {
gameRef.spawnBall();
}
}
}
/// Immediatly and completly [stop]s the ball.
///
/// The [Ball] will no longer be affected by any forces, including it's
/// weight and those emitted from collisions.
void stop() {
body.setType(BodyType.static);
}
/// Allows the [Ball] to be affected by forces.
///
/// If previously [stop]ed, the previous ball's velocity is not kept.
void resume() {
body.setType(BodyType.dynamic);
/// Adds helper methods to the [Ball]
extension BallX on Ball {
/// Returns the controller instance of the ball
// TODO(erickzanardo): Remove the need of an extension.
BallController get controller {
return children.whereType<BallController>().first;
}
}

@ -2,9 +2,10 @@ import 'dart:math' as math;
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template baseboard}
/// Straight, angled board piece to corral the [Ball] towards the [Flipper]s.
/// Wing-shaped board piece to corral the [Ball] towards the [Flipper]s.
/// {@endtemplate}
class Baseboard extends BodyComponent with InitialPosition {
/// {@macro baseboard}
@ -12,41 +13,68 @@ class Baseboard extends BodyComponent with InitialPosition {
required BoardSide side,
}) : _side = side;
/// The width of the [Baseboard].
static const width = 10.0;
/// The height of the [Baseboard].
static const height = 2.0;
/// The size of the [Baseboard].
static final size = Vector2(24.2, 13.5);
/// Whether the [Baseboard] is on the left or right side of the board.
final BoardSide _side;
List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[];
final direction = _side.direction;
final arcsAngle = -1.11 * direction;
const arcsRotation = math.pi / 2.08;
final topCircleShape = CircleShape()..radius = 0.7;
topCircleShape.position.setValues(11.39 * direction, 6.05);
final topCircleFixtureDef = FixtureDef(topCircleShape);
fixturesDef.add(topCircleFixtureDef);
final innerEdgeShape = EdgeShape()
..set(
Vector2(10.86 * direction, 6.45),
Vector2(6.96 * direction, 0.25),
);
final innerEdgeShapeFixtureDef = FixtureDef(innerEdgeShape);
fixturesDef.add(innerEdgeShapeFixtureDef);
final outerEdgeShape = EdgeShape()
..set(
Vector2(11.96 * direction, 5.85),
Vector2(5.48 * direction, -4.85),
);
final outerEdgeShapeFixtureDef = FixtureDef(outerEdgeShape);
fixturesDef.add(outerEdgeShapeFixtureDef);
final upperArcFixtureDefs = Pathway.arc(
center: Vector2(1.76 * direction, 3.25),
width: 0,
radius: 6.1,
angle: arcsAngle,
rotation: arcsRotation,
singleWall: true,
).createFixtureDefs();
fixturesDef.addAll(upperArcFixtureDefs);
final lowerArcFixtureDefs = Pathway.arc(
center: Vector2(1.85 * direction, -2.15),
width: 0,
radius: 4.5,
angle: arcsAngle,
rotation: arcsRotation,
singleWall: true,
).createFixtureDefs();
fixturesDef.addAll(lowerArcFixtureDefs);
final circleShape1 = CircleShape()..radius = Baseboard.height / 2;
circleShape1.position.setValues(
-(Baseboard.width / 2) + circleShape1.radius,
0,
);
final circle1FixtureDef = FixtureDef(circleShape1);
fixturesDef.add(circle1FixtureDef);
final circleShape2 = CircleShape()..radius = Baseboard.height / 2;
circleShape2.position.setValues(
(Baseboard.width / 2) - circleShape2.radius,
0,
);
final circle2FixtureDef = FixtureDef(circleShape2);
fixturesDef.add(circle2FixtureDef);
final rectangle = PolygonShape()
..setAsBoxXY(
(Baseboard.width - Baseboard.height) / 2,
Baseboard.height / 2,
final bottomRectangle = PolygonShape()
..setAsBox(
7,
2,
Vector2(-5.14 * direction, -4.75),
0,
);
final rectangleFixtureDef = FixtureDef(rectangle);
fixturesDef.add(rectangleFixtureDef);
final bottomRectangleFixtureDef = FixtureDef(bottomRectangle);
fixturesDef.add(bottomRectangleFixtureDef);
return fixturesDef;
}
@ -55,7 +83,7 @@ class Baseboard extends BodyComponent with InitialPosition {
Body createBody() {
// TODO(allisonryan0002): share sweeping angle with flipper when components
// are grouped.
const angle = math.pi / 7;
const angle = math.pi / 5;
final bodyDef = BodyDef()
..position = initialPosition

@ -1,5 +1,6 @@
import 'package:flame/components.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template board}
/// The main flat surface of the [PinballGame], where the [Flipper]s,
@ -133,8 +134,8 @@ class _BottomGroupSide extends Component {
final baseboard = Baseboard(side: _side)
..initialPosition = _position +
Vector2(
(Flipper.size.x * direction) - direction,
Flipper.size.y,
(Baseboard.size.x / 1.6 * direction),
Baseboard.size.y - 2,
);
final kicker = Kicker(
side: _side,

@ -8,6 +8,7 @@ import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template bonus_word}
/// Loads all [BonusLetter]s to compose a [BonusWord].

@ -4,12 +4,10 @@ export 'board.dart';
export 'board_side.dart';
export 'bonus_word.dart';
export 'flipper.dart';
export 'initial_position.dart';
export 'jetpack_ramp.dart';
export 'joint_anchor.dart';
export 'kicker.dart';
export 'launcher_ramp.dart';
export 'layer.dart';
export 'pathway.dart';
export 'plunger.dart';
export 'ramp_opening.dart';

@ -6,6 +6,7 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/services.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
const _leftFlipperKeys = [
LogicalKeyboardKey.arrowLeft,

@ -4,6 +4,7 @@ import 'package:flame/components.dart';
import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template jetpack_ramp}
/// Represents the upper left blue ramp of the [Board].

@ -1,5 +1,5 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template joint_anchor}
/// Non visual [BodyComponent] used to hold a [BodyType.dynamic] in [Joint]s

@ -5,6 +5,7 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:geometry/geometry.dart' as geometry show centroid;
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template kicker}
/// Triangular [BodyType.static] body that propels the [Ball] towards the

@ -4,6 +4,7 @@ import 'package:flame/components.dart';
import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template launcher_ramp}
/// The yellow left ramp, where the [Ball] goes through when launched from the

@ -2,7 +2,7 @@ import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:geometry/geometry.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template pathway}
/// [Pathway] creates lines of various shapes.

@ -2,6 +2,7 @@ import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/services.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template plunger}
/// [Plunger] serves as a spring, that shoots the ball on the right side of the

@ -2,6 +2,7 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template ramp_orientation}
/// Determines if a ramp is facing [up] or [down] on the [Board].

@ -1,5 +1,6 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template round_bumper}
/// Circular body that repels a [Ball] on contact, increasing the score.

@ -2,11 +2,12 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template score_points}
/// Specifies the amount of points received on [Ball] collision.
/// {@endtemplate}
mixin ScorePoints on BodyComponent {
mixin ScorePoints<T extends Forge2DGame> on BodyComponent<T> {
/// {@macro score_points}
int get points;
@ -26,6 +27,8 @@ class BallScorePointsCallback extends ContactCallback<Ball, ScorePoints> {
ScorePoints scorePoints,
Contact _,
) {
ball.gameRef.read<GameBloc>().add(Scored(points: scorePoints.points));
ball.controller.gameRef.read<GameBloc>().add(
Scored(points: scorePoints.points),
);
}
}

@ -8,6 +8,7 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/flame/blueprint.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
/// A [Blueprint] which creates the spaceship feature.
class Spaceship extends Forge2DBlueprint {
@ -15,7 +16,7 @@ class Spaceship extends Forge2DBlueprint {
static const radius = 10.0;
@override
void build() {
void build(_) {
final position = Vector2(
PinballGame.boardBounds.left + radius + 15,
PinballGame.boardBounds.center.dy + 30,

@ -4,6 +4,7 @@ import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/components/components.dart';
import 'package:pinball/game/pinball_game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template wall}
/// A continuous generic and [BodyType.static] barrier that divides a game area.
@ -77,6 +78,6 @@ class BottomWall extends Wall {
class BottomWallBallContactCallback extends ContactCallback<Ball, BottomWall> {
@override
void begin(Ball ball, BottomWall wall, Contact contact) {
ball.lost();
ball.controller.lost();
}
}

@ -1,12 +1,13 @@
import 'package:pinball/game/game.dart';
import 'package:pinball/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' as components;
/// Add methods to help loading and caching game assets.
extension PinballGameAssetsX on PinballGame {
/// Pre load the initial assets of the game.
Future<void> preLoadAssets() async {
await Future.wait([
images.load(Assets.images.components.ball.path),
images.load(components.Assets.images.ball.keyName),
images.load(Assets.images.components.flipper.path),
images.load(Assets.images.components.spaceship.androidTop.path),
images.load(Assets.images.components.spaceship.androidBottom.path),

@ -102,11 +102,7 @@ class PinballGame extends Forge2DGame
}
void spawnBall() {
final ball = Ball();
add(
ball
..initialPosition = plunger.body.position + Vector2(0, ball.size.y / 2),
);
addFromBlueprint(BallBlueprint(position: plunger.body.position));
}
}
@ -115,8 +111,6 @@ class DebugPinballGame extends PinballGame with TapDetector {
@override
void onTapUp(TapUpInfo info) {
add(
Ball()..initialPosition = info.eventPosition.game,
);
addFromBlueprint(BallBlueprint(position: info.eventPosition.game));
}
}

@ -15,12 +15,8 @@ class $AssetsImagesGen {
class $AssetsImagesComponentsGen {
const $AssetsImagesComponentsGen();
AssetGenImage get ball =>
const AssetGenImage('assets/images/components/ball.png');
AssetGenImage get flipper =>
const AssetGenImage('assets/images/components/flipper.png');
AssetGenImage get sauce =>
const AssetGenImage('assets/images/components/sauce.png');
$AssetsImagesComponentsSpaceshipGen get spaceship =>
const $AssetsImagesComponentsSpaceshipGen();
}

@ -1 +1,4 @@
include: package:very_good_analysis/analysis_options.2.4.0.yaml
analyzer:
exclude:
- lib/**/*.gen.dart

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

@ -0,0 +1,68 @@
/// GENERATED CODE - DO NOT MODIFY BY HAND
/// *****************************************************
/// FlutterGen
/// *****************************************************
import 'package:flutter/widgets.dart';
class $AssetsImagesGen {
const $AssetsImagesGen();
AssetGenImage get ball => const AssetGenImage('assets/images/ball.png');
}
class Assets {
Assets._();
static const $AssetsImagesGen images = $AssetsImagesGen();
}
class AssetGenImage extends AssetImage {
const AssetGenImage(String assetName)
: super(assetName, package: 'pinball_components');
Image image({
Key? key,
ImageFrameBuilder? frameBuilder,
ImageLoadingBuilder? loadingBuilder,
ImageErrorWidgetBuilder? errorBuilder,
String? semanticLabel,
bool excludeFromSemantics = false,
double? width,
double? height,
Color? color,
BlendMode? colorBlendMode,
BoxFit? fit,
AlignmentGeometry alignment = Alignment.center,
ImageRepeat repeat = ImageRepeat.noRepeat,
Rect? centerSlice,
bool matchTextDirection = false,
bool gaplessPlayback = false,
bool isAntiAlias = false,
FilterQuality filterQuality = FilterQuality.low,
}) {
return Image(
key: key,
image: this,
frameBuilder: frameBuilder,
loadingBuilder: loadingBuilder,
errorBuilder: errorBuilder,
semanticLabel: semanticLabel,
excludeFromSemantics: excludeFromSemantics,
width: width,
height: height,
color: color,
colorBlendMode: colorBlendMode,
fit: fit,
alignment: alignment,
repeat: repeat,
centerSlice: centerSlice,
matchTextDirection: matchTextDirection,
gaplessPlayback: gaplessPlayback,
isAntiAlias: isAntiAlias,
filterQuality: filterQuality,
);
}
String get path => assetName;
}

@ -1,3 +1,4 @@
library pinball_components;
export 'gen/assets.gen.dart';
export 'src/pinball_components.dart';

@ -0,0 +1,72 @@
import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template ball}
/// A solid, [BodyType.dynamic] sphere that rolls and bounces around
/// {@endtemplate}
class Ball<T extends Forge2DGame> extends BodyComponent<T>
with Layered, InitialPosition {
/// {@macro ball_body}
Ball({
required this.baseColor,
}) {
// TODO(ruimiguel): while developing Ball can be launched by clicking mouse,
// and default layer is Layer.all. But on final game Ball will be always be
// be launched from Plunger and LauncherRamp will modify it to Layer.board.
// We need to see what happens if Ball appears from other place like nest
// bumper, it will need to explicit change layer to Layer.board then.
layer = Layer.board;
}
/// The size of the [Ball]
final Vector2 size = Vector2.all(2);
/// The base [Color] used to tint this [Ball]
final Color baseColor;
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(Assets.images.ball.keyName);
final tint = baseColor.withOpacity(0.5);
await add(
SpriteComponent(
sprite: sprite,
size: size,
anchor: Anchor.center,
)..tint(tint),
);
}
@override
Body createBody() {
final shape = CircleShape()..radius = size.x / 2;
final fixtureDef = FixtureDef(shape)..density = 1;
final bodyDef = BodyDef()
..position = initialPosition
..userData = this
..type = BodyType.dynamic;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
/// Immediatly and completly [stop]s the ball.
///
/// The [Ball] will no longer be affected by any forces, including it's
/// weight and those emitted from collisions.
void stop() {
body.setType(BodyType.static);
}
/// Allows the [Ball] to be affected by forces.
///
/// If previously [stop]ed, the previous ball's velocity is not kept.
void resume() {
body.setType(BodyType.dynamic);
}
}

@ -0,0 +1,3 @@
export 'ball.dart';
export 'initial_position.dart';
export 'layer.dart';

@ -1,7 +1 @@
/// {@template pinball_components}
/// Package with the UI game components for the Pinball Game
/// {@endtemplate}
class PinballComponents {
/// {@macro pinball_components}
const PinballComponents();
}
export 'components/components.dart';

@ -7,10 +7,24 @@ environment:
sdk: ">=2.16.0 <3.0.0"
dependencies:
flame: ^1.1.0-releasecandidate.6
flame_forge2d: ^0.9.0-releasecandidate.6
flutter:
sdk: flutter
dev_dependencies:
flame_test: ^1.1.0
flutter_test:
sdk: flutter
mocktail: ^0.2.0
very_good_analysis: ^2.4.0
flutter:
generate: true
assets:
- assets/images/
flutter_gen:
line_length: 80
assets:
package_parameter_enabled: true

@ -0,0 +1,23 @@
<!--
Thanks for contributing!
Provide a description of your changes below and a general summary in the title
Please look at the following checklist to ensure that your PR can be accepted quickly:
-->
## Description
<!--- Describe your changes in detail -->
## Type of Change
<!--- Put an `x` in all the boxes that apply: -->
- [ ] ✨ New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change)
- [ ] 🧹 Code refactor
- [ ] ✅ Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore

@ -0,0 +1,10 @@
name: sandbox
on: [pull_request, push]
jobs:
build:
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
with:
flutter_channel: stable
flutter_version: 2.10.0

@ -0,0 +1,127 @@
# Miscellaneous
*.class
*.lock
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/*
# Visual Studio Code related
.classpath
.project
.settings/
.vscode/*
# Flutter repo-specific
/bin/cache/
/bin/mingit/
/dev/benchmarks/mega_gallery/
/dev/bots/.recipe_deps
/dev/bots/android_tools/
/dev/docs/doc/
/dev/docs/flutter.docs.zip
/dev/docs/lib/
/dev/docs/pubspec.yaml
/dev/integration_tests/**/xcuserdata
/dev/integration_tests/**/Pods
/packages/flutter/coverage/
version
# packages file containing multi-root paths
.packages.generated
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
flutter_*.png
linked_*.ds
unlinked.ds
unlinked_spec.ds
.fvm/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
**/android/key.properties
**/android/.idea/
*.jks
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/.last_build_id
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Coverage
coverage/
# Submodules
!pubspec.lock
packages/**/pubspec.lock
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Exceptions to the above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
!.vscode/extensions.json
!.vscode/launch.json
!.idea/codeStyles/
!.idea/dictionaries/
!.idea/runConfigurations/

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
channel: stable
project_type: app

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Very Good Ventures
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,164 @@
# Sandbox
![coverage][coverage_badge]
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![License: MIT][license_badge]][license_link]
Generated by the [Very Good CLI][very_good_cli_link] 🤖
A sanbox application where components are showcased and developed in an isolated way
---
## Getting Started 🚀
This project contains 3 flavors:
- development
- staging
- production
To run the desired flavor either use the launch configuration in VSCode/Android Studio or use the following commands:
```sh
# Development
$ flutter run --flavor development --target lib/main_development.dart
# Staging
$ flutter run --flavor staging --target lib/main_staging.dart
# Production
$ flutter run --flavor production --target lib/main_production.dart
```
_\*Sandbox works on iOS, Android, Web, and Windows._
---
## Running Tests 🧪
To run all unit and widget tests use the following command:
```sh
$ flutter test --coverage --test-randomize-ordering-seed random
```
To view the generated coverage report you can use [lcov](https://github.com/linux-test-project/lcov).
```sh
# Generate Coverage Report
$ genhtml coverage/lcov.info -o coverage/
# Open Coverage Report
$ open coverage/index.html
```
---
## Working with Translations 🌐
This project relies on [flutter_localizations][flutter_localizations_link] and follows the [official internationalization guide for Flutter][internationalization_link].
### Adding Strings
1. To add a new localizable string, open the `app_en.arb` file at `lib/l10n/arb/app_en.arb`.
```arb
{
"@@locale": "en",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
}
}
```
2. Then add a new key/value and description
```arb
{
"@@locale": "en",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
},
"helloWorld": "Hello World",
"@helloWorld": {
"description": "Hello World Text"
}
}
```
3. Use the new string
```dart
import 'package:sandbox/l10n/l10n.dart';
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Text(l10n.helloWorld);
}
```
### Adding Supported Locales
Update the `CFBundleLocalizations` array in the `Info.plist` at `ios/Runner/Info.plist` to include the new locale.
```xml
...
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>es</string>
</array>
...
```
### Adding Translations
1. For each supported locale, add a new ARB file in `lib/l10n/arb`.
```
├── l10n
│ ├── arb
│ │ ├── app_en.arb
│ │ └── app_es.arb
```
2. Add the translated strings to each `.arb` file:
`app_en.arb`
```arb
{
"@@locale": "en",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
}
}
```
`app_es.arb`
```arb
{
"@@locale": "es",
"counterAppBarTitle": "Contador",
"@counterAppBarTitle": {
"description": "Texto mostrado en la AppBar de la página del contador"
}
}
```
[coverage_badge]: coverage_badge.svg
[flutter_localizations_link]: https://api.flutter.dev/flutter/flutter_localizations/flutter_localizations-library.html
[internationalization_link]: https://flutter.dev/docs/development/accessibility-and-localization/internationalization
[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
[very_good_cli_link]: https://github.com/VeryGoodOpenSource/very_good_cli

@ -0,0 +1,4 @@
include: package:very_good_analysis/analysis_options.2.4.0.yaml
linter:
rules:
public_member_api_docs: false

@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="102" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1" />
<stop offset="1" stop-opacity=".1" />
</linearGradient>
<clipPath id="a">
<rect width="102" height="20" rx="3" fill="#fff" />
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h59v20H0z" />
<path fill="#44cc11" d="M59 0h43v20H59z" />
<path fill="url(#b)" d="M0 0h102v20H0z" />
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
<text x="305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">coverage</text>
<text x="305" y="140" transform="scale(.1)" textLength="490">coverage</text>
<text x="795" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="330">100%</text>
<text x="795" y="140" transform="scale(.1)" textLength="330">100%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,11 @@
import 'package:flame_forge2d/flame_forge2d.dart';
String buildSourceLink(String path) {
return 'https://github.com/VGVentures/pinball/tree/main/packages/pinball_components/sandbox/lib/stories/$path';
}
class BasicGame extends Forge2DGame {
BasicGame() {
images.prefix = '';
}
}

@ -0,0 +1,16 @@
// 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';
void main() {
final dashbook = Dashbook(theme: ThemeData.dark());
addBallStories(dashbook);
runApp(dashbook);
}

@ -0,0 +1,18 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic.dart';
void addBallStories(Dashbook dashbook) {
dashbook.storiesOf('Ball').add(
'Basic',
(context) => GameWidget(
game: BasicBallGame(
color: context.colorProperty('color', Colors.blue),
),
),
codeLink: buildSourceLink('ball/basic.dart'),
info: BasicBallGame.info,
);
}

@ -0,0 +1,22 @@
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class BasicBallGame extends BasicGame with TapDetector {
BasicBallGame({ required this.color });
static const info = '''
Basic example of how a Ball works, tap anywhere on the
screen to spawn a ball into the game.
''';
final Color color;
@override
void onTapUp(TapUpInfo info) {
add(Ball(baseColor: color)
..initialPosition = info.eventPosition.game,
);
}
}

@ -0,0 +1,446 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
dashbook:
dependency: "direct main"
description:
name: dashbook
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.7"
device_frame:
dependency: transitive
description:
name: device_frame
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.2"
flame:
dependency: "direct main"
description:
name: flame
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0-releasecandidate.6"
flame_forge2d:
dependency: "direct main"
description:
name: flame_forge2d
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.0-releasecandidate.6"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_colorpicker:
dependency: transitive
description:
name: flutter_colorpicker
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
flutter_markdown:
dependency: transitive
description:
name: flutter_markdown
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.9"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
forge2d:
dependency: transitive
description:
name: forge2d
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.0"
freezed_annotation:
dependency: transitive
description:
name: freezed_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3"
json_annotation:
dependency: transitive
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "4.4.0"
markdown:
dependency: transitive
description:
name: markdown
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.1"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.11"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
ordered_set:
dependency: transitive
description:
name: ordered_set
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.5"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
pinball_components:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "1.0.0+1"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.4"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.13"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.11"
shared_preferences_ios:
dependency: transitive
description:
name: shared_preferences_ios
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.8"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
url_launcher:
dependency: transitive
description:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.20"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.15"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.15"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.9"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
very_good_analysis:
dependency: "direct dev"
description:
name: very_good_analysis
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.4"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0+1"
sdks:
dart: ">=2.16.0 <3.0.0"
flutter: ">=2.10.0"

@ -0,0 +1,24 @@
name: sandbox
description: A sanbox application where components are showcased and developed in an isolated way
version: 1.0.0+1
publish_to: none
environment:
sdk: ">=2.16.0 <3.0.0"
dependencies:
dashbook: ^0.1.7
flame: ^1.1.0-releasecandidate.6
flame_forge2d: ^0.9.0-releasecandidate.6
flutter:
sdk: flutter
pinball_components:
path: ../
dev_dependencies:
flutter_test:
sdk: flutter
very_good_analysis: ^2.4.0
flutter:
uses-material-design: true

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@ -0,0 +1,104 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="sandbox">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>sandbox</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
scriptTag.src = 'main.dart.js';
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing || reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.',
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</script>
</body>
</html>

@ -0,0 +1,35 @@
{
"name": "sandbox",
"short_name": "sandbox",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

@ -0,0 +1,7 @@
import 'package:flame_forge2d/flame_forge2d.dart';
class TestGame extends Forge2DGame {
TestGame() {
images.prefix = '';
}
}

@ -0,0 +1,162 @@
// ignore_for_file: cascade_invocations
import 'package:flame_forge2d/flame_forge2d.dart';
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 '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
group('Ball', () {
flameTester.test(
'loads correctly',
(game) async {
final ball = Ball(baseColor: Colors.blue);
await game.ready();
await game.ensureAdd(ball);
expect(game.contains(ball), isTrue);
},
);
group('body', () {
flameTester.test(
'is dynamic',
(game) async {
final ball = Ball(baseColor: Colors.blue);
await game.ensureAdd(ball);
expect(ball.body.bodyType, equals(BodyType.dynamic));
},
);
group('can be moved', () {
flameTester.test('by its weight', (game) async {
final ball = Ball(baseColor: Colors.blue);
await game.ensureAdd(ball);
game.update(1);
expect(ball.body.position, isNot(equals(ball.initialPosition)));
});
flameTester.test('by applying velocity', (game) async {
final ball = Ball(baseColor: Colors.blue);
await game.ensureAdd(ball);
ball.body.gravityScale = 0;
ball.body.linearVelocity.setValues(10, 10);
game.update(1);
expect(ball.body.position, isNot(equals(ball.initialPosition)));
});
});
});
group('fixture', () {
flameTester.test(
'exists',
(game) async {
final ball = Ball(baseColor: Colors.blue);
await game.ensureAdd(ball);
expect(ball.body.fixtures[0], isA<Fixture>());
},
);
flameTester.test(
'is dense',
(game) async {
final ball = Ball(baseColor: Colors.blue);
await game.ensureAdd(ball);
final fixture = ball.body.fixtures[0];
expect(fixture.density, greaterThan(0));
},
);
flameTester.test(
'shape is circular',
(game) async {
final ball = Ball(baseColor: Colors.blue);
await game.ensureAdd(ball);
final fixture = ball.body.fixtures[0];
expect(fixture.shape.shapeType, equals(ShapeType.circle));
expect(fixture.shape.radius, equals(1));
},
);
flameTester.test(
'has Layer.all as default filter maskBits',
(game) async {
final ball = Ball(baseColor: Colors.blue);
await game.ready();
await game.ensureAdd(ball);
await game.ready();
final fixture = ball.body.fixtures[0];
expect(fixture.filterData.maskBits, equals(Layer.board.maskBits));
},
);
});
group('stop', () {
group("can't be moved", () {
flameTester.test('by its weight', (game) async {
final ball = Ball(baseColor: Colors.blue);
await game.ensureAdd(ball);
ball.stop();
game.update(1);
expect(ball.body.position, equals(ball.initialPosition));
});
});
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', () {
group('can move', () {
flameTester.test(
'by its weight when previously stopped',
(game) async {
final ball = Ball(baseColor: Colors.blue);
await game.ensureAdd(ball);
ball.stop();
ball.resume();
game.update(1);
expect(ball.body.position, isNot(equals(ball.initialPosition)));
},
);
flameTester.test(
'by applying velocity when previously stopped',
(game) async {
final ball = Ball(baseColor: Colors.blue);
await game.ensureAdd(ball);
ball.stop();
ball.resume();
ball.body.gravityScale = 0;
ball.body.linearVelocity.setValues(10, 10);
game.update(1);
expect(ball.body.position, isNot(equals(ball.initialPosition)));
},
);
});
});
});
}

@ -3,7 +3,7 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
class TestBodyComponent extends BodyComponent with InitialPosition {
@override

@ -4,7 +4,7 @@ import 'dart:math' as math;
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
class TestBodyComponent extends BodyComponent with Layered {
@override

@ -1,11 +0,0 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
void main() {
group('PinballComponents', () {
test('can be instantiated', () {
expect(PinballComponents(), isNotNull);
});
});
}

@ -392,6 +392,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
pinball_components:
dependency: "direct main"
description:
path: "packages/pinball_components"
relative: true
source: path
version: "1.0.0+1"
pinball_theme:
dependency: "direct main"
description:

@ -23,6 +23,8 @@ dependencies:
intl: ^0.17.0
leaderboard_repository:
path: packages/leaderboard_repository
pinball_components:
path: packages/pinball_components
pinball_theme:
path: packages/pinball_theme

@ -1,5 +1,4 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/flame/blueprint.dart';
@ -9,7 +8,7 @@ import '../helpers/helpers.dart';
class MyBlueprint extends Blueprint {
@override
void build() {
void build(_) {
add(Component());
addAll([Component(), Component()]);
}
@ -17,7 +16,7 @@ class MyBlueprint extends Blueprint {
class MyForge2dBlueprint extends Forge2DBlueprint {
@override
void build() {
void build(_) {
addContactCallback(MockContactCallback());
addAllContactCallback([MockContactCallback(), MockContactCallback()]);
}
@ -26,7 +25,7 @@ class MyForge2dBlueprint extends Forge2DBlueprint {
void main() {
group('Blueprint', () {
test('components can be added to it', () {
final blueprint = MyBlueprint()..build();
final blueprint = MyBlueprint()..build(MockPinballGame());
expect(blueprint.components.length, equals(3));
});
@ -59,7 +58,7 @@ void main() {
});
test('callbacks can be added to it', () {
final blueprint = MyForge2dBlueprint()..build();
final blueprint = MyForge2dBlueprint()..build(MockPinballGame());
expect(blueprint.callbacks.length, equals(3));
});
@ -92,12 +91,5 @@ void main() {
);
},
);
test('throws assertion error when used on a non Forge2dGame', () {
expect(
() => MyForge2dBlueprint().attach(FlameGame()),
throwsAssertionError,
);
});
});
}

@ -1,110 +1,17 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGameTest.create);
group('Ball', () {
flameTester.test(
'loads correctly',
(game) async {
final ball = Ball();
await game.ready();
await game.ensureAdd(ball);
expect(game.contains(ball), isTrue);
},
);
group('body', () {
flameTester.test(
'is dynamic',
(game) async {
final ball = Ball();
await game.ensureAdd(ball);
expect(ball.body.bodyType, equals(BodyType.dynamic));
},
);
group('can be moved', () {
flameTester.test('by its weight', (game) async {
final ball = Ball();
await game.ensureAdd(ball);
game.update(1);
expect(ball.body.position, isNot(equals(ball.initialPosition)));
});
flameTester.test('by applying velocity', (game) async {
final ball = Ball();
await game.ensureAdd(ball);
ball.body.gravityScale = 0;
ball.body.linearVelocity.setValues(10, 10);
game.update(1);
expect(ball.body.position, isNot(equals(ball.initialPosition)));
});
});
});
group('fixture', () {
flameTester.test(
'exists',
(game) async {
final ball = Ball();
await game.ensureAdd(ball);
expect(ball.body.fixtures[0], isA<Fixture>());
},
);
flameTester.test(
'is dense',
(game) async {
final ball = Ball();
await game.ensureAdd(ball);
final fixture = ball.body.fixtures[0];
expect(fixture.density, greaterThan(0));
},
);
flameTester.test(
'shape is circular',
(game) async {
final ball = Ball();
await game.ensureAdd(ball);
final fixture = ball.body.fixtures[0];
expect(fixture.shape.shapeType, equals(ShapeType.circle));
expect(fixture.shape.radius, equals(1));
},
);
flameTester.test(
'has Layer.all as default filter maskBits',
(game) async {
final ball = Ball();
await game.ready();
await game.ensureAdd(ball);
await game.ready();
final fixture = ball.body.fixtures[0];
expect(fixture.filterData.maskBits, equals(Layer.board.maskBits));
},
);
});
group('lost', () {
late GameBloc gameBloc;
@ -124,7 +31,7 @@ void main() {
(game, tester) async {
await game.ready();
game.children.whereType<Ball>().first.lost();
game.children.whereType<Ball>().first.controller.lost();
await tester.pump();
verify(() => gameBloc.add(const BallLost())).called(1);
@ -136,7 +43,7 @@ void main() {
(game, tester) async {
await game.ready();
game.children.whereType<Ball>().first.lost();
game.children.whereType<Ball>().first.controller.lost();
await game.ready(); // Making sure that all additions are done
expect(
@ -162,7 +69,7 @@ void main() {
);
await game.ready();
game.children.whereType<Ball>().first.lost();
game.children.whereType<Ball>().first.controller.lost();
await tester.pump();
expect(
@ -172,60 +79,5 @@ void main() {
},
);
});
group('stop', () {
group("can't be moved", () {
flameTester.test('by its weight', (game) async {
final ball = Ball();
await game.ensureAdd(ball);
ball.stop();
game.update(1);
expect(ball.body.position, equals(ball.initialPosition));
});
});
flameTester.test('by applying velocity', (game) async {
final ball = Ball();
await game.ensureAdd(ball);
ball.stop();
ball.body.linearVelocity.setValues(10, 10);
game.update(1);
expect(ball.body.position, equals(ball.initialPosition));
});
});
group('resume', () {
group('can move', () {
flameTester.test(
'by its weight when previously stopped',
(game) async {
final ball = Ball();
await game.ensureAdd(ball);
ball.stop();
ball.resume();
game.update(1);
expect(ball.body.position, isNot(equals(ball.initialPosition)));
},
);
flameTester.test(
'by applying velocity when previously stopped',
(game) async {
final ball = Ball();
await game.ensureAdd(ball);
ball.stop();
ball.resume();
ball.body.gravityScale = 0;
ball.body.linearVelocity.setValues(10, 10);
game.update(1);
expect(ball.body.position, isNot(equals(ball.initialPosition)));
},
);
});
});
});
}

@ -61,14 +61,14 @@ void main() {
group('fixtures', () {
flameTester.test(
'has three',
'has six',
(game) async {
final baseboard = Baseboard(
side: BoardSide.left,
);
await game.ensureAdd(baseboard);
expect(baseboard.body.fixtures.length, equals(3));
expect(baseboard.body.fixtures.length, equals(6));
},
);
});

@ -4,9 +4,11 @@ import 'dart:collection';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
@ -81,7 +83,7 @@ void main() {
final flipper = Flipper(
side: BoardSide.left,
);
final ball = Ball();
final ball = Ball(baseColor: Colors.white);
await game.ready();
await game.ensureAddAll([flipper, ball]);

@ -4,6 +4,7 @@ import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockingjay/mockingjay.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';

@ -1,9 +1,11 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
class MockBall extends Mock implements Ball {}
import '../../helpers/helpers.dart';
class MockGameBloc extends Mock implements GameBloc {}
@ -28,12 +30,16 @@ void main() {
late PinballGame game;
late GameBloc bloc;
late Ball ball;
late ComponentSet componentSet;
late BallController ballController;
late FakeScorePoints fakeScorePoints;
setUp(() {
game = MockPinballGame();
bloc = MockGameBloc();
ball = MockBall();
componentSet = MockComponentSet();
ballController = MockBallController();
fakeScorePoints = FakeScorePoints();
});
@ -45,7 +51,10 @@ void main() {
test(
'emits Scored event with points',
() {
when<PinballGame>(() => ball.gameRef).thenReturn(game);
when(() => componentSet.whereType<BallController>())
.thenReturn([ballController]);
when(() => ball.children).thenReturn(componentSet);
when<Forge2DGame>(() => ballController.gameRef).thenReturn(game);
when<GameBloc>(game.read).thenReturn(bloc);
BallScorePointsCallback().begin(

@ -2,6 +2,7 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';

@ -17,11 +17,14 @@ void main() {
test(
'removes the ball on begin contact when the wall is a bottom one',
() {
final game = MockPinballGame();
final wall = MockBottomWall();
final ballController = MockBallController();
final ball = MockBall();
final componentSet = MockComponentSet();
when(() => ball.gameRef).thenReturn(game);
when(() => componentSet.whereType<BallController>())
.thenReturn([ballController]);
when(() => ball.children).thenReturn(componentSet);
BottomWallBallContactCallback()
// Remove once https://github.com/flame-engine/flame/pull/1415
@ -29,7 +32,7 @@ void main() {
..end(MockBall(), MockBottomWall(), MockContact())
..begin(ball, wall, MockContact());
verify(ball.lost).called(1);
verify(ballController.lost).called(1);
},
);
});

@ -5,6 +5,7 @@ import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../helpers/helpers.dart';

@ -1,3 +1,4 @@
import 'package:flame/components.dart';
import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/foundation.dart';
@ -6,6 +7,7 @@ import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/theme/theme.dart';
import 'package:pinball_components/pinball_components.dart';
class MockPinballGame extends Mock implements PinballGame {}
@ -17,6 +19,8 @@ class MockBody extends Mock implements Body {}
class MockBall extends Mock implements Ball {}
class MockBallController extends Mock implements BallController {}
class MockContact extends Mock implements Contact {}
class MockContactCallback extends Mock
@ -62,3 +66,5 @@ class MockFixture extends Mock implements Fixture {}
class MockSpaceshipEntrance extends Mock implements SpaceshipEntrance {}
class MockSpaceshipHole extends Mock implements SpaceshipHole {}
class MockComponentSet extends Mock implements ComponentSet {}

File diff suppressed because one or more lines are too long

@ -33,6 +33,7 @@
<title>Pinball</title>
<link rel="manifest" href="manifest.json">
<script src="/__/firebase/8.9.1/firebase-app.js"></script>
<script src="/__/firebase/8.9.1/firebase-firestore.js"></script>
<script src="/__/firebase/init.js"></script>
</head>

Loading…
Cancel
Save