mirror of https://github.com/flutter/pinball.git
feat: adding leaderboard display (#352)
* feat: adding leaderboard display * feat: pr suggestions * fix: conflicts * lint Co-authored-by: Alejandro Santiago <dev@alestiago.com>pull/358/head
parent
32498e2cd5
commit
f8b6234ab4
@ -1,4 +1,5 @@
|
||||
export 'initials_input_display.dart';
|
||||
export 'initials_submission_failure_display.dart';
|
||||
export 'initials_submission_success_display.dart';
|
||||
export 'leaderboard_display.dart';
|
||||
export 'loading_display.dart';
|
||||
|
@ -0,0 +1,120 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:leaderboard_repository/leaderboard_repository.dart';
|
||||
import 'package:pinball/l10n/l10n.dart';
|
||||
import 'package:pinball/leaderboard/models/leader_board_entry.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
import 'package:pinball_ui/pinball_ui.dart';
|
||||
|
||||
final _titleTextPaint = TextPaint(
|
||||
style: const TextStyle(
|
||||
fontSize: 2,
|
||||
color: PinballColors.red,
|
||||
fontFamily: PinballFonts.pixeloidSans,
|
||||
),
|
||||
);
|
||||
|
||||
final _bodyTextPaint = TextPaint(
|
||||
style: const TextStyle(
|
||||
fontSize: 1.8,
|
||||
color: PinballColors.white,
|
||||
fontFamily: PinballFonts.pixeloidSans,
|
||||
),
|
||||
);
|
||||
|
||||
/// {@template leaderboard_display}
|
||||
/// Component that builds the leaderboard list of the Backbox.
|
||||
/// {@endtemplate}
|
||||
class LeaderboardDisplay extends PositionComponent with HasGameRef {
|
||||
/// {@macro leaderboard_display}
|
||||
LeaderboardDisplay({required List<LeaderboardEntryData> entries})
|
||||
: _entries = entries;
|
||||
|
||||
final List<LeaderboardEntryData> _entries;
|
||||
|
||||
double _calcY(int i) => (i * 3.2) + 3.2;
|
||||
|
||||
static const _columns = [-15.0, 0.0, 15.0];
|
||||
|
||||
String _rank(int number) {
|
||||
switch (number) {
|
||||
case 1:
|
||||
return '${number}st';
|
||||
case 2:
|
||||
return '${number}nd';
|
||||
case 3:
|
||||
return '${number}rd';
|
||||
default:
|
||||
return '${number}th';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
position = Vector2(0, -30);
|
||||
|
||||
final l10n = readProvider<AppLocalizations>();
|
||||
final ranking = _entries.take(5).toList();
|
||||
await add(
|
||||
PositionComponent(
|
||||
position: Vector2(0, 4),
|
||||
children: [
|
||||
PositionComponent(
|
||||
children: [
|
||||
TextComponent(
|
||||
text: l10n.rank,
|
||||
textRenderer: _titleTextPaint,
|
||||
position: Vector2(_columns[0], 0),
|
||||
anchor: Anchor.center,
|
||||
),
|
||||
TextComponent(
|
||||
text: l10n.score,
|
||||
textRenderer: _titleTextPaint,
|
||||
position: Vector2(_columns[1], 0),
|
||||
anchor: Anchor.center,
|
||||
),
|
||||
TextComponent(
|
||||
text: l10n.name,
|
||||
textRenderer: _titleTextPaint,
|
||||
position: Vector2(_columns[2], 0),
|
||||
anchor: Anchor.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
for (var i = 0; i < ranking.length; i++)
|
||||
PositionComponent(
|
||||
children: [
|
||||
TextComponent(
|
||||
text: _rank(i + 1),
|
||||
textRenderer: _bodyTextPaint,
|
||||
position: Vector2(_columns[0], _calcY(i)),
|
||||
anchor: Anchor.center,
|
||||
),
|
||||
TextComponent(
|
||||
text: ranking[i].score.formatScore(),
|
||||
textRenderer: _bodyTextPaint,
|
||||
position: Vector2(_columns[1], _calcY(i)),
|
||||
anchor: Anchor.center,
|
||||
),
|
||||
SpriteComponent.fromImage(
|
||||
gameRef.images.fromCache(
|
||||
ranking[i].character.toTheme.leaderboardIcon.keyName,
|
||||
),
|
||||
anchor: Anchor.center,
|
||||
size: Vector2(1.8, 1.8),
|
||||
position: Vector2(_columns[2] - 2.5, _calcY(i) + .25),
|
||||
),
|
||||
TextComponent(
|
||||
text: ranking[i].playerInitials,
|
||||
textRenderer: _bodyTextPaint,
|
||||
position: Vector2(_columns[2] + 1, _calcY(i)),
|
||||
anchor: Anchor.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/forge2d_game.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:leaderboard_repository/leaderboard_repository.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:pinball/game/components/backbox/displays/leaderboard_display.dart';
|
||||
import 'package:pinball/l10n/l10n.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
import 'package:pinball_theme/pinball_theme.dart';
|
||||
|
||||
class _MockAppLocalizations extends Mock implements AppLocalizations {
|
||||
@override
|
||||
String get rank => 'rank';
|
||||
|
||||
@override
|
||||
String get score => 'score';
|
||||
|
||||
@override
|
||||
String get name => 'name';
|
||||
}
|
||||
|
||||
class _TestGame extends Forge2DGame {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
images.prefix = '';
|
||||
await images.load(const AndroidTheme().leaderboardIcon.keyName);
|
||||
}
|
||||
|
||||
Future<void> pump(LeaderboardDisplay component) {
|
||||
return ensureAdd(
|
||||
FlameProvider.value(
|
||||
_MockAppLocalizations(),
|
||||
children: [component],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('LeaderboardDisplay', () {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
final flameTester = FlameTester(_TestGame.new);
|
||||
|
||||
flameTester.test('renders the titles', (game) async {
|
||||
await game.pump(LeaderboardDisplay(entries: const []));
|
||||
|
||||
final textComponents =
|
||||
game.descendants().whereType<TextComponent>().toList();
|
||||
expect(textComponents.length, equals(3));
|
||||
expect(textComponents[0].text, equals('rank'));
|
||||
expect(textComponents[1].text, equals('score'));
|
||||
expect(textComponents[2].text, equals('name'));
|
||||
});
|
||||
|
||||
flameTester.test('renders the entries', (game) async {
|
||||
await game.pump(
|
||||
LeaderboardDisplay(
|
||||
entries: const [
|
||||
LeaderboardEntryData(
|
||||
playerInitials: 'AAA',
|
||||
score: 123,
|
||||
character: CharacterType.android,
|
||||
),
|
||||
LeaderboardEntryData(
|
||||
playerInitials: 'BBB',
|
||||
score: 1234,
|
||||
character: CharacterType.android,
|
||||
),
|
||||
LeaderboardEntryData(
|
||||
playerInitials: 'CCC',
|
||||
score: 12345,
|
||||
character: CharacterType.android,
|
||||
),
|
||||
LeaderboardEntryData(
|
||||
playerInitials: 'DDD',
|
||||
score: 12346,
|
||||
character: CharacterType.android,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
for (final text in [
|
||||
'AAA',
|
||||
'BBB',
|
||||
'CCC',
|
||||
'DDD',
|
||||
'1st',
|
||||
'2nd',
|
||||
'3rd',
|
||||
'4th'
|
||||
]) {
|
||||
expect(
|
||||
game
|
||||
.descendants()
|
||||
.whereType<TextComponent>()
|
||||
.where((textComponent) => textComponent.text == text)
|
||||
.length,
|
||||
equals(1),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in new issue