feat: final screen for the initials submission error (#393)

pull/401/head
Erick 3 years ago committed by GitHub
parent 75feeaae26
commit 6aef28ed8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -89,7 +89,18 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef {
} else if (state is InitialsSuccessState) { } else if (state is InitialsSuccessState) {
_display.add(InitialsSubmissionSuccessDisplay()); _display.add(InitialsSubmissionSuccessDisplay());
} else if (state is InitialsFailureState) { } else if (state is InitialsFailureState) {
_display.add(InitialsSubmissionFailureDisplay()); _display.add(
InitialsSubmissionFailureDisplay(
onDismissed: () {
_bloc.add(
PlayerInitialsRequested(
score: state.score,
character: state.character,
),
);
},
),
);
} }
} }

@ -56,7 +56,12 @@ class BackboxBloc extends Bloc<BackboxEvent, BackboxState> {
emit(InitialsSuccessState()); emit(InitialsSuccessState());
} catch (error, stackTrace) { } catch (error, stackTrace) {
addError(error, stackTrace); addError(error, stackTrace);
emit(InitialsFailureState()); emit(
InitialsFailureState(
score: event.score,
character: event.character,
),
);
} }
} }

@ -62,6 +62,17 @@ class InitialsSuccessState extends BackboxState {
/// State when the initials submission failed. /// State when the initials submission failed.
class InitialsFailureState extends BackboxState { class InitialsFailureState extends BackboxState {
const InitialsFailureState({
required this.score,
required this.character,
});
/// Player's score.
final int score;
/// Player's character.
final CharacterTheme character;
@override @override
List<Object?> get props => []; List<Object?> get props => [score, character];
} }

@ -1,27 +1,47 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
final _bodyTextPaint = TextPaint( final _bodyTextPaint = TextPaint(
style: const TextStyle( style: const TextStyle(
fontSize: 3, fontSize: 1.8,
color: PinballColors.white, color: PinballColors.white,
fontFamily: PinballFonts.pixeloidSans, fontFamily: PinballFonts.pixeloidSans,
fontWeight: FontWeight.w400,
), ),
); );
/// {@template initials_submission_failure_display} /// {@template initials_submission_failure_display}
/// [Backbox] display for when a failure occurs during initials submission. /// [Backbox] display for when a failure occurs during initials submission.
/// {@endtemplate} /// {@endtemplate}
class InitialsSubmissionFailureDisplay extends TextComponent { class InitialsSubmissionFailureDisplay extends Component {
/// {@macro initials_submission_failure_display}
InitialsSubmissionFailureDisplay({
required this.onDismissed,
});
final VoidCallback onDismissed;
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); final l10n = readProvider<AppLocalizations>();
position = Vector2(0, -10);
anchor = Anchor.center; await addAll([
text = 'Failure!'; ErrorComponent.bold(
textRenderer = _bodyTextPaint; label: l10n.initialsErrorTitle,
position: Vector2(0, -20),
),
TextComponent(
text: l10n.initialsErrorMessage,
anchor: Anchor.center,
position: Vector2(0, -12),
textRenderer: _bodyTextPaint,
),
TimerComponent(period: 4, onTick: onDismissed),
]);
} }
} }

@ -152,6 +152,14 @@
"@enter": { "@enter": {
"description": "Text shown on the mobile controls enter button" "description": "Text shown on the mobile controls enter button"
}, },
"initialsErrorTitle": "Uh-oh... well, that didnt work",
"@enter": {
"description": "Title shown when the initials submission fails"
},
"initialsErrorMessage": "Please try a different combination of letters",
"@initialsErrorMessage": {
"description": "Message on shown when the initials submission fails"
},
"leaderboardErrorMessage": "No connection. Leaderboard and sharing functionality is unavailable.", "leaderboardErrorMessage": "No connection. Leaderboard and sharing functionality is unavailable.",
"@leaderboardErrorMessage": { "@leaderboardErrorMessage": {
"description": "Text shown when the leaderboard had an error while loading" "description": "Text shown when the leaderboard had an error while loading"

@ -100,6 +100,12 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
@override @override
String get loading => ''; String get loading => '';
@override
String get initialsErrorTitle => '';
@override
String get initialsErrorMessage => '';
@override @override
String get leaderboardErrorMessage => ''; String get leaderboardErrorMessage => '';
} }
@ -273,7 +279,10 @@ void main() {
whenListen( whenListen(
bloc, bloc,
Stream<BackboxState>.empty(), Stream<BackboxState>.empty(),
initialState: InitialsFailureState(), initialState: InitialsFailureState(
score: 0,
character: theme.DashTheme(),
),
); );
final backbox = Backbox.test( final backbox = Backbox.test(
bloc: bloc, bloc: bloc,
@ -354,7 +363,12 @@ void main() {
backbox.removeFromParent(); backbox.removeFromParent();
await game.ready(); await game.ready();
streamController.add(InitialsFailureState()); streamController.add(
InitialsFailureState(
score: 10,
character: theme.DashTheme(),
),
);
await game.ready(); await game.ready();
expect( expect(
@ -366,5 +380,36 @@ void main() {
); );
}, },
); );
flameTester.test(
'adds PlayerInitialsSubmitted when the timer is finished',
(game) async {
final initialState = InitialsFailureState(
score: 10,
character: theme.DashTheme(),
);
whenListen(
bloc,
Stream<BackboxState>.fromIterable([]),
initialState: initialState,
);
final backbox = Backbox.test(
bloc: bloc,
platformHelper: platformHelper,
);
await game.pump(backbox);
game.update(4);
verify(
() => bloc.add(
PlayerInitialsRequested(
score: 10,
character: theme.DashTheme(),
),
),
).called(1);
},
);
}); });
} }

@ -113,7 +113,7 @@ void main() {
), ),
expect: () => [ expect: () => [
LoadingState(), LoadingState(),
InitialsFailureState(), InitialsFailureState(score: 10, character: DashTheme()),
], ],
); );
}); });

@ -124,11 +124,56 @@ void main() {
group('InitialsFailureState', () { group('InitialsFailureState', () {
test('can be instantiated', () { test('can be instantiated', () {
expect(InitialsFailureState(), isNotNull); expect(
InitialsFailureState(
score: 10,
character: AndroidTheme(),
),
isNotNull,
);
}); });
test('supports value comparison', () { test('supports value comparison', () {
expect(InitialsFailureState(), equals(InitialsFailureState())); expect(
InitialsFailureState(
score: 10,
character: AndroidTheme(),
),
equals(
InitialsFailureState(
score: 10,
character: AndroidTheme(),
),
),
);
expect(
InitialsFailureState(
score: 10,
character: AndroidTheme(),
),
isNot(
equals(
InitialsFailureState(
score: 12,
character: AndroidTheme(),
),
),
),
);
expect(
InitialsFailureState(
score: 10,
character: AndroidTheme(),
),
isNot(
equals(
InitialsFailureState(
score: 10,
character: DashTheme(),
),
),
),
);
}); });
}); });
}); });

@ -4,18 +4,74 @@ import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/components/backbox/displays/initials_submission_failure_display.dart'; import 'package:pinball/game/components/backbox/displays/initials_submission_failure_display.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_components/gen/assets.gen.dart';
import 'package:pinball_flame/pinball_flame.dart';
class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
await super.onLoad();
images.prefix = '';
await images.loadAll(
[
Assets.images.errorBackground.keyName,
],
);
}
Future<void> pump(InitialsSubmissionFailureDisplay component) {
return ensureAdd(
FlameProvider.value(
_MockAppLocalizations(),
children: [component],
),
);
}
}
class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get initialsErrorTitle => 'Title';
@override
String get initialsErrorMessage => 'Message';
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('InitialsSubmissionFailureDisplay', () { group('InitialsSubmissionFailureDisplay', () {
final flameTester = FlameTester(Forge2DGame.new); final flameTester = FlameTester(_TestGame.new);
flameTester.test('renders correctly', (game) async { flameTester.test('renders correctly', (game) async {
await game.ensureAdd(InitialsSubmissionFailureDisplay()); await game.pump(
InitialsSubmissionFailureDisplay(
onDismissed: () {},
),
);
final component = game.firstChild<TextComponent>(); expect(
expect(component, isNotNull); game
expect(component?.text, equals('Failure!')); .descendants()
.where(
(component) =>
component is TextComponent && component.text == 'Title',
)
.length,
equals(1),
);
expect(
game
.descendants()
.where(
(component) =>
component is TextComponent && component.text == 'Message',
)
.length,
equals(1),
);
}); });
}); });
} }

Loading…
Cancel
Save