Merge branch 'main' into feat/animate-dino-mouth

pull/229/head
Allison Ryan 3 years ago
commit f2534b4b5c

@ -72,6 +72,20 @@ class FetchPlayerRankingException extends LeaderboardException {
); );
} }
/// {@template fetch_prohibited_initials_exception}
/// Exception thrown when failure occurs while fetching prohibited initials.
/// {@endtemplate}
class FetchProhibitedInitialsException extends LeaderboardException {
/// {@macro fetch_prohibited_initials_exception}
const FetchProhibitedInitialsException(
Object error,
StackTrace stackTrace,
) : super(
error,
stackTrace,
);
}
/// {@template leaderboard_repository} /// {@template leaderboard_repository}
/// Repository to access leaderboard data in Firebase Cloud Firestore. /// Repository to access leaderboard data in Firebase Cloud Firestore.
/// {@endtemplate} /// {@endtemplate}
@ -152,4 +166,25 @@ class LeaderboardRepository {
throw FetchPlayerRankingException(error, stackTrace); throw FetchPlayerRankingException(error, stackTrace);
} }
} }
/// Determines if the given [initials] are allowed.
Future<bool> areInitialsAllowed({required String initials}) async {
// Initials can only be three uppercase A-Z letters
final initialsRegex = RegExp(r'^[A-Z]{3}$');
if (!initialsRegex.hasMatch(initials)) {
return false;
}
try {
final document = await _firebaseFirestore
.collection('prohibitedInitials')
.doc('list')
.get();
final prohibitedInitials =
document.get('prohibitedInitials') as List<String>;
return !prohibitedInitials.contains(initials);
} on Exception catch (error, stackTrace) {
throw FetchProhibitedInitialsException(error, stackTrace);
}
}
} }

@ -21,6 +21,9 @@ class MockQueryDocumentSnapshot extends Mock
class MockDocumentReference extends Mock class MockDocumentReference extends Mock
implements DocumentReference<Map<String, dynamic>> {} implements DocumentReference<Map<String, dynamic>> {}
class MockDocumentSnapshot extends Mock
implements DocumentSnapshot<Map<String, dynamic>> {}
void main() { void main() {
group('LeaderboardRepository', () { group('LeaderboardRepository', () {
late FirebaseFirestore firestore; late FirebaseFirestore firestore;
@ -223,5 +226,94 @@ void main() {
); );
}); });
}); });
group('areInitialsAllowed', () {
late LeaderboardRepository leaderboardRepository;
late CollectionReference<Map<String, dynamic>> collectionReference;
late DocumentReference<Map<String, dynamic>> documentReference;
late DocumentSnapshot<Map<String, dynamic>> documentSnapshot;
setUp(() async {
collectionReference = MockCollectionReference();
documentReference = MockDocumentReference();
documentSnapshot = MockDocumentSnapshot();
leaderboardRepository = LeaderboardRepository(firestore);
when(() => firestore.collection('prohibitedInitials'))
.thenReturn(collectionReference);
when(() => collectionReference.doc('list'))
.thenReturn(documentReference);
when(() => documentReference.get())
.thenAnswer((_) async => documentSnapshot);
when<dynamic>(() => documentSnapshot.get('prohibitedInitials'))
.thenReturn(['BAD']);
});
test('returns true if initials are three letters and allowed', () async {
final isUsernameAllowedResponse =
await leaderboardRepository.areInitialsAllowed(
initials: 'ABC',
);
expect(
isUsernameAllowedResponse,
isTrue,
);
});
test(
'returns false if initials are shorter than 3 characters',
() async {
final areInitialsAllowedResponse =
await leaderboardRepository.areInitialsAllowed(initials: 'AB');
expect(areInitialsAllowedResponse, isFalse);
},
);
test(
'returns false if initials are longer than 3 characters',
() async {
final areInitialsAllowedResponse =
await leaderboardRepository.areInitialsAllowed(initials: 'ABCD');
expect(areInitialsAllowedResponse, isFalse);
},
);
test(
'returns false if initials contain a lowercase letter',
() async {
final areInitialsAllowedResponse =
await leaderboardRepository.areInitialsAllowed(initials: 'AbC');
expect(areInitialsAllowedResponse, isFalse);
},
);
test(
'returns false if initials contain a special character',
() async {
final areInitialsAllowedResponse =
await leaderboardRepository.areInitialsAllowed(initials: 'A@C');
expect(areInitialsAllowedResponse, isFalse);
},
);
test('returns false if initials are forbidden', () async {
final areInitialsAllowedResponse =
await leaderboardRepository.areInitialsAllowed(initials: 'BAD');
expect(areInitialsAllowedResponse, isFalse);
});
test(
'throws FetchProhibitedInitialsException when Exception occurs '
'when trying to retrieve information from firestore',
() async {
when(() => firestore.collection('prohibitedInitials'))
.thenThrow(Exception('oops'));
expect(
() => leaderboardRepository.areInitialsAllowed(initials: 'ABC'),
throwsA(isA<FetchProhibitedInitialsException>()),
);
},
);
});
}); });
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 28 KiB

@ -34,24 +34,21 @@ class _DinoTopWall extends BodyComponent with InitialPosition {
} }
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[];
final topStraightShape = EdgeShape() final topStraightShape = EdgeShape()
..set( ..set(
Vector2(28.65, -35.1), Vector2(28.65, -35.1),
Vector2(29.5, -35.1), Vector2(29.5, -35.1),
); );
final topStraightFixtureDef = FixtureDef(topStraightShape); final topStraightFixtureDef = FixtureDef(topStraightShape);
fixturesDef.add(topStraightFixtureDef);
final topCurveShape = BezierCurveShape( final topCurveShape = BezierCurveShape(
controlPoints: [ controlPoints: [
topStraightShape.vertex1, topStraightShape.vertex1,
Vector2(17.4, -26.38), Vector2(18.8, -27),
Vector2(25.5, -20.7), Vector2(26.6, -21),
], ],
); );
fixturesDef.add(FixtureDef(topCurveShape)); final topCurveFixtureDef = FixtureDef(topCurveShape);
final middleCurveShape = BezierCurveShape( final middleCurveShape = BezierCurveShape(
controlPoints: [ controlPoints: [
@ -60,16 +57,16 @@ class _DinoTopWall extends BodyComponent with InitialPosition {
Vector2(26.8, -19.5), Vector2(26.8, -19.5),
], ],
); );
fixturesDef.add(FixtureDef(middleCurveShape)); final middleCurveFixtureDef = FixtureDef(middleCurveShape);
final bottomCurveShape = BezierCurveShape( final bottomCurveShape = BezierCurveShape(
controlPoints: [ controlPoints: [
middleCurveShape.vertices.last, middleCurveShape.vertices.last,
Vector2(21.5, -15.8), Vector2(23, -15),
Vector2(25.8, -14.8), Vector2(27, -15),
], ],
); );
fixturesDef.add(FixtureDef(bottomCurveShape)); final bottomCurveFixtureDef = FixtureDef(bottomCurveShape);
final bottomStraightShape = EdgeShape() final bottomStraightShape = EdgeShape()
..set( ..set(
@ -77,9 +74,14 @@ class _DinoTopWall extends BodyComponent with InitialPosition {
Vector2(31, -14.5), Vector2(31, -14.5),
); );
final bottomStraightFixtureDef = FixtureDef(bottomStraightShape); final bottomStraightFixtureDef = FixtureDef(bottomStraightShape);
fixturesDef.add(bottomStraightFixtureDef);
return fixturesDef; return [
topStraightFixtureDef,
topCurveFixtureDef,
middleCurveFixtureDef,
bottomCurveFixtureDef,
bottomStraightFixtureDef,
];
} }
@override @override
@ -106,12 +108,14 @@ class _DinoTopWallSpriteComponent extends SpriteComponent with HasGameRef {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
Assets.images.dino.topWall.keyName, gameRef.images.fromCache(
Assets.images.dino.topWall.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
position = Vector2(22, -41.8); position = Vector2(22.8, -38.9);
} }
} }
@ -129,69 +133,56 @@ class _DinoBottomWall extends BodyComponent with InitialPosition {
} }
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[];
const restitution = 1.0; const restitution = 1.0;
final topStraightControlPoints = [
Vector2(32.4, -8.8),
Vector2(25, -7.7),
];
final topStraightShape = EdgeShape() final topStraightShape = EdgeShape()
..set( ..set(
topStraightControlPoints.first, Vector2(32.4, -8.8),
topStraightControlPoints.last, Vector2(25, -7.7),
); );
final topStraightFixtureDef = FixtureDef( final topStraightFixtureDef = FixtureDef(
topStraightShape, topStraightShape,
restitution: restitution, restitution: restitution,
); );
fixturesDef.add(topStraightFixtureDef);
final topLeftCurveControlPoints = [
topStraightControlPoints.last,
Vector2(21.8, -7),
Vector2(29.5, 13.8),
];
final topLeftCurveShape = BezierCurveShape( final topLeftCurveShape = BezierCurveShape(
controlPoints: topLeftCurveControlPoints, controlPoints: [
topStraightShape.vertex2,
Vector2(21.8, -7),
Vector2(29.8, 13.8),
],
); );
final topLeftCurveFixtureDef = FixtureDef( final topLeftCurveFixtureDef = FixtureDef(
topLeftCurveShape, topLeftCurveShape,
restitution: restitution, restitution: restitution,
); );
fixturesDef.add(topLeftCurveFixtureDef);
final bottomLeftStraightControlPoints = [
topLeftCurveControlPoints.last,
Vector2(31.8, 44.1),
];
final bottomLeftStraightShape = EdgeShape() final bottomLeftStraightShape = EdgeShape()
..set( ..set(
bottomLeftStraightControlPoints.first, topLeftCurveShape.vertices.last,
bottomLeftStraightControlPoints.last, Vector2(31.9, 44.1),
); );
final bottomLeftStraightFixtureDef = FixtureDef( final bottomLeftStraightFixtureDef = FixtureDef(
bottomLeftStraightShape, bottomLeftStraightShape,
restitution: restitution, restitution: restitution,
); );
fixturesDef.add(bottomLeftStraightFixtureDef);
final bottomStraightControlPoints = [
bottomLeftStraightControlPoints.last,
Vector2(37.8, 44.1),
];
final bottomStraightShape = EdgeShape() final bottomStraightShape = EdgeShape()
..set( ..set(
bottomStraightControlPoints.first, bottomLeftStraightShape.vertex2,
bottomStraightControlPoints.last, Vector2(37.8, 44.1),
); );
final bottomStraightFixtureDef = FixtureDef( final bottomStraightFixtureDef = FixtureDef(
bottomStraightShape, bottomStraightShape,
restitution: restitution, restitution: restitution,
); );
fixturesDef.add(bottomStraightFixtureDef);
return fixturesDef; return [
topStraightFixtureDef,
topLeftCurveFixtureDef,
bottomLeftStraightFixtureDef,
bottomStraightFixtureDef,
];
} }
@override @override
@ -212,8 +203,10 @@ class _DinoBottomWallSpriteComponent extends SpriteComponent with HasGameRef {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
Assets.images.dino.bottomWall.keyName, gameRef.images.fromCache(
Assets.images.dino.bottomWall.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;

@ -29,6 +29,7 @@ void main() {
addLaunchRampStories(dashbook); addLaunchRampStories(dashbook);
addScoreTextStories(dashbook); addScoreTextStories(dashbook);
addBackboardStories(dashbook); addBackboardStories(dashbook);
addDinoWallStories(dashbook);
runApp(dashbook); runApp(dashbook);
} }

@ -0,0 +1,31 @@
import 'dart:async';
import 'package:flame/input.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class DinoWallGame extends BallGame {
DinoWallGame() : super();
static const description = '''
Shows how DinoWalls are rendered.
- Activate the "trace" parameter to overlay the body.
- Tap anywhere on the screen to spawn a ball into the game.
''';
@override
Future<void> onLoad() async {
await super.onLoad();
await images.loadAll([
Assets.images.dino.topWall.keyName,
Assets.images.dino.bottomWall.keyName,
]);
await addFromBlueprint(DinoWalls());
camera.followVector2(Vector2.zero());
await traceAllBodies();
}
}

@ -0,0 +1,11 @@
import 'package:dashbook/dashbook.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/dino_wall/dino_wall_game.dart';
void addDinoWallStories(Dashbook dashbook) {
dashbook.storiesOf('DinoWall').addGame(
title: 'Traced',
description: DinoWallGame.description,
gameBuilder: (_) => DinoWallGame(),
);
}

@ -4,6 +4,7 @@ export 'ball/stories.dart';
export 'baseboard/stories.dart'; export 'baseboard/stories.dart';
export 'boundaries/stories.dart'; export 'boundaries/stories.dart';
export 'chrome_dino/stories.dart'; export 'chrome_dino/stories.dart';
export 'dino_wall/stories.dart';
export 'effects/stories.dart'; export 'effects/stories.dart';
export 'flipper/stories.dart'; export 'flipper/stories.dart';
export 'flutter_forest/stories.dart'; export 'flutter_forest/stories.dart';

@ -11,15 +11,23 @@ import '../../helpers/helpers.dart';
void main() { void main() {
group('DinoWalls', () { group('DinoWalls', () {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new); final assets = [
Assets.images.dino.topWall.keyName,
Assets.images.dino.bottomWall.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
flameTester.testGameWidget( flameTester.testGameWidget(
'renders correctly', 'renders correctly',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.addFromBlueprint(DinoWalls()); await game.addFromBlueprint(DinoWalls());
await game.ready();
game.camera.followVector2(Vector2.zero()); game.camera.followVector2(Vector2.zero());
game.camera.zoom = 6.5; game.camera.zoom = 6.5;
await game.ready();
await tester.pump();
}, },
verify: (game, tester) async { verify: (game, tester) async {
await expectLater( await expectLater(

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 148 KiB

@ -1,3 +1,4 @@
library share_repository; library share_repository;
export 'src/models/models.dart';
export 'src/share_repository.dart'; export 'src/share_repository.dart';

@ -0,0 +1,8 @@
/// The platform that is being used to share a score.
enum SharePlatform {
/// Twitter platform.
twitter,
/// Facebook platform.
facebook,
}

@ -1,7 +1,30 @@
import 'package:share_repository/share_repository.dart';
/// {@template share_repository} /// {@template share_repository}
/// Repository to facilitate sharing scores. /// Repository to facilitate sharing scores.
/// {@endtemplate} /// {@endtemplate}
class ShareRepository { class ShareRepository {
/// {@macro share_repository} /// {@macro share_repository}
const ShareRepository(); const ShareRepository({
required String appUrl,
}) : _appUrl = appUrl;
final String _appUrl;
/// Returns a url to share the [value] on the given [platform].
///
/// The returned url can be opened using the [url_launcher](https://pub.dev/packages/url_launcher) package.
String shareText({
required String value,
required SharePlatform platform,
}) {
final encodedUrl = Uri.encodeComponent(_appUrl);
final encodedShareText = Uri.encodeComponent(value);
switch (platform) {
case SharePlatform.twitter:
return 'https://twitter.com/intent/tweet?url=$encodedUrl&text=$encodedShareText';
case SharePlatform.facebook:
return 'https://www.facebook.com/sharer.php?u=$encodedUrl&quote=$encodedShareText';
}
}
} }

@ -4,8 +4,38 @@ import 'package:test/test.dart';
void main() { void main() {
group('ShareRepository', () { group('ShareRepository', () {
const appUrl = 'https://fakeurl.com/';
late ShareRepository shareRepository;
setUp(() {
shareRepository = ShareRepository(appUrl: appUrl);
});
test('can be instantiated', () { test('can be instantiated', () {
expect(ShareRepository(), isNotNull); expect(ShareRepository(appUrl: appUrl), isNotNull);
});
group('shareText', () {
const value = 'hello world!';
test('returns the correct share url for twitter', () async {
const shareTextUrl =
'https://twitter.com/intent/tweet?url=https%3A%2F%2Ffakeurl.com%2F&text=hello%20world!';
final shareTextResult = shareRepository.shareText(
value: value,
platform: SharePlatform.twitter,
);
expect(shareTextResult, equals(shareTextUrl));
});
test('returns the correct share url for facebook', () async {
const shareTextUrl =
'https://www.facebook.com/sharer.php?u=https%3A%2F%2Ffakeurl.com%2F&quote=hello%20world!';
final shareTextResult = shareRepository.shareText(
value: value,
platform: SharePlatform.facebook,
);
expect(shareTextResult, equals(shareTextUrl));
});
}); });
}); });
} }

@ -56,6 +56,8 @@ void main() {
Assets.images.boundary.bottom.keyName, Assets.images.boundary.bottom.keyName,
Assets.images.slingshot.upper.keyName, Assets.images.slingshot.upper.keyName,
Assets.images.slingshot.lower.keyName, Assets.images.slingshot.lower.keyName,
Assets.images.dino.topWall.keyName,
Assets.images.dino.bottomWall.keyName,
]; ];
final flameTester = FlameTester(() => PinballTestGame(assets)); final flameTester = FlameTester(() => PinballTestGame(assets));
final debugModeFlameTester = FlameTester(() => DebugPinballTestGame(assets)); final debugModeFlameTester = FlameTester(() => DebugPinballTestGame(assets));

Loading…
Cancel
Save