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}
/// Repository to access leaderboard data in Firebase Cloud Firestore.
/// {@endtemplate}
@ -152,4 +166,25 @@ class LeaderboardRepository {
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
implements DocumentReference<Map<String, dynamic>> {}
class MockDocumentSnapshot extends Mock
implements DocumentSnapshot<Map<String, dynamic>> {}
void main() {
group('LeaderboardRepository', () {
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() {
final fixturesDef = <FixtureDef>[];
final topStraightShape = EdgeShape()
..set(
Vector2(28.65, -35.1),
Vector2(29.5, -35.1),
);
final topStraightFixtureDef = FixtureDef(topStraightShape);
fixturesDef.add(topStraightFixtureDef);
final topCurveShape = BezierCurveShape(
controlPoints: [
topStraightShape.vertex1,
Vector2(17.4, -26.38),
Vector2(25.5, -20.7),
Vector2(18.8, -27),
Vector2(26.6, -21),
],
);
fixturesDef.add(FixtureDef(topCurveShape));
final topCurveFixtureDef = FixtureDef(topCurveShape);
final middleCurveShape = BezierCurveShape(
controlPoints: [
@ -60,16 +57,16 @@ class _DinoTopWall extends BodyComponent with InitialPosition {
Vector2(26.8, -19.5),
],
);
fixturesDef.add(FixtureDef(middleCurveShape));
final middleCurveFixtureDef = FixtureDef(middleCurveShape);
final bottomCurveShape = BezierCurveShape(
controlPoints: [
middleCurveShape.vertices.last,
Vector2(21.5, -15.8),
Vector2(25.8, -14.8),
Vector2(23, -15),
Vector2(27, -15),
],
);
fixturesDef.add(FixtureDef(bottomCurveShape));
final bottomCurveFixtureDef = FixtureDef(bottomCurveShape);
final bottomStraightShape = EdgeShape()
..set(
@ -77,9 +74,14 @@ class _DinoTopWall extends BodyComponent with InitialPosition {
Vector2(31, -14.5),
);
final bottomStraightFixtureDef = FixtureDef(bottomStraightShape);
fixturesDef.add(bottomStraightFixtureDef);
return fixturesDef;
return [
topStraightFixtureDef,
topCurveFixtureDef,
middleCurveFixtureDef,
bottomCurveFixtureDef,
bottomStraightFixtureDef,
];
}
@override
@ -106,12 +108,14 @@ class _DinoTopWallSpriteComponent extends SpriteComponent with HasGameRef {
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.dino.topWall.keyName,
),
);
this.sprite = sprite;
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() {
final fixturesDef = <FixtureDef>[];
const restitution = 1.0;
final topStraightControlPoints = [
Vector2(32.4, -8.8),
Vector2(25, -7.7),
];
final topStraightShape = EdgeShape()
..set(
topStraightControlPoints.first,
topStraightControlPoints.last,
Vector2(32.4, -8.8),
Vector2(25, -7.7),
);
final topStraightFixtureDef = FixtureDef(
topStraightShape,
restitution: restitution,
);
fixturesDef.add(topStraightFixtureDef);
final topLeftCurveControlPoints = [
topStraightControlPoints.last,
Vector2(21.8, -7),
Vector2(29.5, 13.8),
];
final topLeftCurveShape = BezierCurveShape(
controlPoints: topLeftCurveControlPoints,
controlPoints: [
topStraightShape.vertex2,
Vector2(21.8, -7),
Vector2(29.8, 13.8),
],
);
final topLeftCurveFixtureDef = FixtureDef(
topLeftCurveShape,
restitution: restitution,
);
fixturesDef.add(topLeftCurveFixtureDef);
final bottomLeftStraightControlPoints = [
topLeftCurveControlPoints.last,
Vector2(31.8, 44.1),
];
final bottomLeftStraightShape = EdgeShape()
..set(
bottomLeftStraightControlPoints.first,
bottomLeftStraightControlPoints.last,
topLeftCurveShape.vertices.last,
Vector2(31.9, 44.1),
);
final bottomLeftStraightFixtureDef = FixtureDef(
bottomLeftStraightShape,
restitution: restitution,
);
fixturesDef.add(bottomLeftStraightFixtureDef);
final bottomStraightControlPoints = [
bottomLeftStraightControlPoints.last,
Vector2(37.8, 44.1),
];
final bottomStraightShape = EdgeShape()
..set(
bottomStraightControlPoints.first,
bottomStraightControlPoints.last,
bottomLeftStraightShape.vertex2,
Vector2(37.8, 44.1),
);
final bottomStraightFixtureDef = FixtureDef(
bottomStraightShape,
restitution: restitution,
);
fixturesDef.add(bottomStraightFixtureDef);
return fixturesDef;
return [
topStraightFixtureDef,
topLeftCurveFixtureDef,
bottomLeftStraightFixtureDef,
bottomStraightFixtureDef,
];
}
@override
@ -212,8 +203,10 @@ class _DinoBottomWallSpriteComponent extends SpriteComponent with HasGameRef {
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.dino.bottomWall.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;

@ -29,6 +29,7 @@ void main() {
addLaunchRampStories(dashbook);
addScoreTextStories(dashbook);
addBackboardStories(dashbook);
addDinoWallStories(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 'boundaries/stories.dart';
export 'chrome_dino/stories.dart';
export 'dino_wall/stories.dart';
export 'effects/stories.dart';
export 'flipper/stories.dart';
export 'flutter_forest/stories.dart';

@ -11,15 +11,23 @@ import '../../helpers/helpers.dart';
void main() {
group('DinoWalls', () {
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(
'renders correctly',
setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.addFromBlueprint(DinoWalls());
await game.ready();
game.camera.followVector2(Vector2.zero());
game.camera.zoom = 6.5;
await game.ready();
await tester.pump();
},
verify: (game, tester) async {
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;
export 'src/models/models.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}
/// Repository to facilitate sharing scores.
/// {@endtemplate}
class ShareRepository {
/// {@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() {
group('ShareRepository', () {
const appUrl = 'https://fakeurl.com/';
late ShareRepository shareRepository;
setUp(() {
shareRepository = ShareRepository(appUrl: appUrl);
});
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.slingshot.upper.keyName,
Assets.images.slingshot.lower.keyName,
Assets.images.dino.topWall.keyName,
Assets.images.dino.bottomWall.keyName,
];
final flameTester = FlameTester(() => PinballTestGame(assets));
final debugModeFlameTester = FlameTester(() => DebugPinballTestGame(assets));

Loading…
Cancel
Save