From 639f59af82e348b4aad74917dbe6872919d9a2d8 Mon Sep 17 00:00:00 2001 From: Erick Zanardo Date: Sun, 8 May 2022 19:39:35 -0300 Subject: [PATCH] feat: adding leaderboard pagination buttons --- .../backbox/displays/leaderboard_display.dart | 211 +++++++++++++---- lib/game/game_assets.dart | 2 + .../images/display_arrows/arrow_left.png | Bin 0 -> 866 bytes .../images/display_arrows/arrow_right.png | Bin 0 -> 842 bytes .../lib/gen/assets.gen.dart | 12 + .../lib/src/components/arrow_icon.dart | 49 ++++ .../lib/src/components/components.dart | 1 + packages/pinball_components/pubspec.yaml | 1 + .../pinball_components/sandbox/lib/main.dart | 1 + .../stories/arrow_icon/arrow_icon_game.dart | 37 +++ .../lib/stories/arrow_icon/stories.dart | 11 + .../sandbox/lib/stories/stories.dart | 1 + .../test/helpers/test_game.dart | 5 + .../test/src/components/arrow_icon_test.dart | 96 ++++++++ .../src/components/golden/arrow_icon_left.png | Bin 0 -> 22848 bytes .../components/golden/arrow_icon_right.png | Bin 0 -> 22823 bytes .../game/components/backbox/backbox_test.dart | 9 +- .../displays/leaderboard_display_test.dart | 213 +++++++++++++++--- .../game_bloc_status_listener_test.dart | 5 +- 19 files changed, 576 insertions(+), 78 deletions(-) create mode 100644 packages/pinball_components/assets/images/display_arrows/arrow_left.png create mode 100644 packages/pinball_components/assets/images/display_arrows/arrow_right.png create mode 100644 packages/pinball_components/lib/src/components/arrow_icon.dart create mode 100644 packages/pinball_components/sandbox/lib/stories/arrow_icon/arrow_icon_game.dart create mode 100644 packages/pinball_components/sandbox/lib/stories/arrow_icon/stories.dart create mode 100644 packages/pinball_components/test/src/components/arrow_icon_test.dart create mode 100644 packages/pinball_components/test/src/components/golden/arrow_icon_left.png create mode 100644 packages/pinball_components/test/src/components/golden/arrow_icon_right.png diff --git a/lib/game/components/backbox/displays/leaderboard_display.dart b/lib/game/components/backbox/displays/leaderboard_display.dart index ac7a798d..9f0729e1 100644 --- a/lib/game/components/backbox/displays/leaderboard_display.dart +++ b/lib/game/components/backbox/displays/leaderboard_display.dart @@ -1,4 +1,5 @@ import 'package:flame/components.dart'; +import 'package:flame/effects.dart'; import 'package:flutter/material.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/l10n/l10n.dart'; @@ -23,6 +24,23 @@ final _bodyTextPaint = TextPaint( ), ); +double _calcY(int i) => (i * 3.2) + 3.2; + +const _columns = [-14.0, 0.0, 14.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'; + } +} + /// {@template leaderboard_display} /// Component that builds the leaderboard list of the Backbox. /// {@endtemplate} @@ -33,21 +51,47 @@ class LeaderboardDisplay extends PositionComponent with HasGameRef { final List _entries; - double _calcY(int i) => (i * 3.2) + 3.2; + _MovePageArrow _findArrow({required bool active}) { + return descendants() + .whereType<_MovePageArrow>() + .firstWhere((arrow) => arrow.active == active); + } - static const _columns = [-15.0, 0.0, 15.0]; + void _changePage(List ranking, int offset) { + final current = descendants().whereType<_RankingPage>().single; + final activeArrow = _findArrow(active: true); + final inactiveArrow = _findArrow(active: false); - 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'; - } + activeArrow.active = false; + + current.add( + ScaleEffect.to( + Vector2(0, 1), + EffectController( + duration: 0.5, + curve: Curves.easeIn, + ), + )..onFinishCallback = () { + current.removeFromParent(); + inactiveArrow.active = true; + firstChild()?.add( + _RankingPage( + ranking: ranking, + offset: offset, + ) + ..scale = Vector2(0, 1) + ..add( + ScaleEffect.to( + Vector2(1, 1), + EffectController( + duration: 0.5, + curve: Curves.easeIn, + ), + ), + ), + ); + }, + ); } @override @@ -60,6 +104,20 @@ class LeaderboardDisplay extends PositionComponent with HasGameRef { PositionComponent( position: Vector2(0, 4), children: [ + _MovePageArrow( + position: Vector2(20, 9), + onTap: () { + _changePage(_entries.sublist(5), 5); + }, + ), + _MovePageArrow( + position: Vector2(-20, 9), + direction: ArrowIconDirection.left, + active: false, + onTap: () { + _changePage(_entries.take(5).toList(), 0); + }, + ), PositionComponent( children: [ TextComponent( @@ -82,39 +140,104 @@ class LeaderboardDisplay extends PositionComponent with HasGameRef { ), ], ), - 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, - ), - ], - ), + _RankingPage( + ranking: ranking, + offset: 0, + ), ], ), ); } } + +class _RankingPage extends PositionComponent with HasGameRef { + _RankingPage({ + required this.ranking, + required this.offset, + }) : super(children: []); + + final List ranking; + final int offset; + + @override + Future onLoad() async { + await addAll([ + for (var i = 0; i < ranking.length; i++) + PositionComponent( + children: [ + TextComponent( + text: _rank(i + 1 + offset), + 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] - 3, _calcY(i) + .25), + ), + TextComponent( + text: ranking[i].playerInitials, + textRenderer: _bodyTextPaint, + position: Vector2(_columns[2] + 1, _calcY(i)), + anchor: Anchor.center, + ), + ], + ), + ]); + } +} + +class _MovePageArrow extends PositionComponent { + _MovePageArrow({ + required Vector2 position, + required this.onTap, + this.direction = ArrowIconDirection.right, + bool active = true, + }) : super( + position: position, + children: [ + if (active) + ArrowIcon( + position: Vector2.zero(), + direction: direction, + onTap: onTap, + ), + SequenceEffect( + [ + ScaleEffect.to( + Vector2.all(1.2), + EffectController(duration: 1), + ), + ScaleEffect.to(Vector2.all(1), EffectController(duration: 1)), + ], + infinite: true, + ), + ], + ); + + final ArrowIconDirection direction; + final VoidCallback onTap; + + bool get active => children.whereType().isNotEmpty; + set active(bool value) { + if (value) { + add(ArrowIcon( + position: Vector2.zero(), + direction: direction, + onTap: onTap, + ),); + } else { + firstChild()?.removeFromParent(); + } + } +} diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index 2f386695..15a52afb 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -141,6 +141,8 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.skillShot.pin.keyName), images.load(components.Assets.images.skillShot.lit.keyName), images.load(components.Assets.images.skillShot.dimmed.keyName), + images.load(components.Assets.images.displayArrows.arrowLeft.keyName), + images.load(components.Assets.images.displayArrows.arrowRight.keyName), images.load(androidTheme.leaderboardIcon.keyName), images.load(androidTheme.background.keyName), images.load(androidTheme.ball.keyName), diff --git a/packages/pinball_components/assets/images/display_arrows/arrow_left.png b/packages/pinball_components/assets/images/display_arrows/arrow_left.png new file mode 100644 index 0000000000000000000000000000000000000000..e851bef45f1a47128243021eb3a57c8eb5c8e7cf GIT binary patch literal 866 zcmV-o1D*VdP)ZItZyCP6e?mh+RR>6(p%3=L%w1VCaCWz)A;nplE{_i^mF|X28aH zzZr(P=e-5ibNhr4M9+-1wy;CaX(7BLrH2*-(1HLb^!ymN?FDTSWHckLS>_-$e!oFI z%d&>nj^HN0)n#58S`a`B0?h3)7x3A*trql>7Q!S2aZT}z`p&_waT^KW(AseYxd+#H z@VdIpYex$LXhDEUhVHHhY-!k3Ywi~iYN#E)Z$Yn@GIURJ8GnHV$UmvQz&z0S;%Xtb zj_8fc&HFRZ_da*8fUbBu7k^3pe}ZuvJKH7XDE;{yl{*P{5<9^m@+n-#ZwSZkQS`YV zK)(0qg6vXqYYzdmAb=JGNOBqX6XPb<8+yA2XGZiKQv9C?vI!GMk{-z&J!VpF60og} zegpZ&fB7@$S3Wim59Utpikm3JR@AzTl$-eq`ZcwV^f44&?hBWj-R*rKAEZv_3up8)F2Dd@)UQ*ui>Yg>|! zSx~u-|@T27mTfFd`Xp*yL6kSA$-hmKICuY z{A!AycZs>DnlZ~Uc9Pkv8E4~Sxs$N!GH(wp2%rT4Qdba_g2qjYZ{I7>m}`jTj&CBm z6wk4@Brojk=4Zm#M#xb{bTRi_Pye}#mv;E{zq_Hr2h4`MKCK0-ORCn376j0O0J@|K zm}KEDUwq_G1$>S!siH^gT`%e~D?keZXhDFvUk~{JpSww%fldoy)|U_^Y-G6>-}}%4 sc!}3kdAxECT15*%dkCNf0kj~%Ka8AjBU+_#Hvj+t07*qoM6N<$g1HckQ~&?~ literal 0 HcmV?d00001 diff --git a/packages/pinball_components/assets/images/display_arrows/arrow_right.png b/packages/pinball_components/assets/images/display_arrows/arrow_right.png new file mode 100644 index 0000000000000000000000000000000000000000..c8d5d2f2bce8695f1ea9d5484b9a847cb0a8391a GIT binary patch literal 842 zcmV-Q1GW5#P)#U0bPb^+0}}o zaAP`7oiUdQW5Xhjh}96n5<*x)h@6UQ8K#QlI*c-oM?z8E;7H_TyBedm8xr^tefM!8 zQ8iA5RF?_U1IfSe0$2?pEFpv?gviZhN|JX`<6HMy>6J7=-{C+OA@CZ*O~^PW{Vr~- zBK>{jT`|PI2(iW8-ge~QXj_oqDaJk8#13Msoj0H61plPV z_=I;pRznC&2w@2^rV*Fn#)>$BJX>(J{(;tIxLRMJeSSFWXyP)T5@U-Kd?Q^PtO7Y( zi2dHT4pw$#aW8SDpAO=4{05ubK@ZMW|K z8x?HPDYe-J@Ue_YIkp*cgdjhoK#kk!yDQA%8~px5N0p2dEvh zI3J~Z^^DjDcjHnzX7(C_uo^;GLI_I;Aue-8np9;sp)*pjt*d7^y}{X zGu^d~5~D@}xgFeHkt~T4z3htd?LWWlev!BT+2HL+Ri0Qv2ulcIN2-XN4(@6lbBB>5 zRVQ^wSHs)qA$ej6AuJ(8*5BM=qH%;Hk(0}alZ+jEn&0s{6})}wo+p+N!V*IK1>fq2 Uv}qFNd;kCd07*qoM6N<$g2Wzvo&W#< literal 0 HcmV?d00001 diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index f233596c..0708878f 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -23,6 +23,9 @@ class $AssetsImagesGen { $AssetsImagesDashGen get dash => const $AssetsImagesDashGen(); $AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen(); + $AssetsImagesDisplayArrowsGen get displayArrows => + const $AssetsImagesDisplayArrowsGen(); + /// File path: assets/images/error_background.png AssetGenImage get errorBackground => const AssetGenImage('assets/images/error_background.png'); @@ -140,6 +143,15 @@ class $AssetsImagesDinoGen { const AssetGenImage('assets/images/dino/top_wall_tunnel.png'); } +class $AssetsImagesDisplayArrowsGen { + const $AssetsImagesDisplayArrowsGen(); + + AssetGenImage get arrowLeft => + const AssetGenImage('assets/images/display_arrows/arrow_left.png'); + AssetGenImage get arrowRight => + const AssetGenImage('assets/images/display_arrows/arrow_right.png'); +} + class $AssetsImagesFlapperGen { const $AssetsImagesFlapperGen(); diff --git a/packages/pinball_components/lib/src/components/arrow_icon.dart b/packages/pinball_components/lib/src/components/arrow_icon.dart new file mode 100644 index 00000000..0dc33b10 --- /dev/null +++ b/packages/pinball_components/lib/src/components/arrow_icon.dart @@ -0,0 +1,49 @@ +import 'package:flame/components.dart'; +import 'package:flame/input.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball_components/pinball_components.dart'; + +/// enum with the available directions for an [ArrowIcon]. +enum ArrowIconDirection { + /// Left. + left, + + /// Right. + right, +} + +/// {@template arrow_icon} +/// A [SpriteComponent] that renders a simple arrow icon. +/// {@endtemplate} +class ArrowIcon extends SpriteComponent with Tappable, HasGameRef { + /// {@macro arrow_icon} + ArrowIcon({ + required Vector2 position, + required this.direction, + required this.onTap, + }) : super(position: position); + + final ArrowIconDirection direction; + final VoidCallback onTap; + + @override + Future onLoad() async { + anchor = Anchor.center; + final sprite = Sprite( + gameRef.images.fromCache( + direction == ArrowIconDirection.left + ? Assets.images.displayArrows.arrowLeft.keyName + : Assets.images.displayArrows.arrowRight.keyName, + ), + ); + + size = sprite.originalSize / 20; + this.sprite = sprite; + } + + @override + bool onTapUp(TapUpInfo info) { + onTap(); + return true; + } +} diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index 75ba12dc..76d4e189 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -2,6 +2,7 @@ export 'android_animatronic.dart'; export 'android_bumper/android_bumper.dart'; export 'android_spaceship/android_spaceship.dart'; export 'arcade_background/arcade_background.dart'; +export 'arrow_icon.dart'; export 'ball/ball.dart'; export 'baseboard.dart'; export 'board_background_sprite_component.dart'; diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index 3301a0fc..60651eb2 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -93,6 +93,7 @@ flutter: - assets/images/backbox/button/ - assets/images/flapper/ - assets/images/skill_shot/ + - assets/images/display_arrows/ flutter_gen: line_length: 80 diff --git a/packages/pinball_components/sandbox/lib/main.dart b/packages/pinball_components/sandbox/lib/main.dart index 714bbee5..fb948a89 100644 --- a/packages/pinball_components/sandbox/lib/main.dart +++ b/packages/pinball_components/sandbox/lib/main.dart @@ -21,6 +21,7 @@ void main() { addScoreStories(dashbook); addMultiballStories(dashbook); addMultipliersStories(dashbook); + addArrowIconStories(dashbook); runApp(dashbook); } diff --git a/packages/pinball_components/sandbox/lib/stories/arrow_icon/arrow_icon_game.dart b/packages/pinball_components/sandbox/lib/stories/arrow_icon/arrow_icon_game.dart new file mode 100644 index 00000000..23af63b0 --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/arrow_icon/arrow_icon_game.dart @@ -0,0 +1,37 @@ +import 'package:flame/game.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:sandbox/common/games.dart'; + +class ArrowIconGame extends AssetsGame with HasTappables { + ArrowIconGame() + : super( + imagesFileNames: [ + Assets.images.displayArrows.arrowLeft.keyName, + Assets.images.displayArrows.arrowRight.keyName, + ], + ); + + static const description = 'Shows how ArrowIcons are rendered.'; + + @override + Future onLoad() async { + await super.onLoad(); + camera.followVector2(Vector2.zero()); + + await add( + ArrowIcon( + position: Vector2.zero(), + direction: ArrowIconDirection.left, + onTap: () {}, + ), + ); + + await add( + ArrowIcon( + position: Vector2(0, 20), + direction: ArrowIconDirection.right, + onTap: () {}, + ), + ); + } +} diff --git a/packages/pinball_components/sandbox/lib/stories/arrow_icon/stories.dart b/packages/pinball_components/sandbox/lib/stories/arrow_icon/stories.dart new file mode 100644 index 00000000..05a8a8ff --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/arrow_icon/stories.dart @@ -0,0 +1,11 @@ +import 'package:dashbook/dashbook.dart'; +import 'package:sandbox/common/common.dart'; +import 'package:sandbox/stories/arrow_icon/arrow_icon_game.dart'; + +void addArrowIconStories(Dashbook dashbook) { + dashbook.storiesOf('ArrowIcon').addGame( + title: 'Basic', + description: ArrowIconGame.description, + gameBuilder: (context) => ArrowIconGame(), + ); +} diff --git a/packages/pinball_components/sandbox/lib/stories/stories.dart b/packages/pinball_components/sandbox/lib/stories/stories.dart index 0a514eb9..8a165693 100644 --- a/packages/pinball_components/sandbox/lib/stories/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/stories.dart @@ -1,4 +1,5 @@ export 'android_acres/stories.dart'; +export 'arrow_icon/stories.dart'; export 'ball/stories.dart'; export 'bottom_group/stories.dart'; export 'boundaries/stories.dart'; diff --git a/packages/pinball_components/test/helpers/test_game.dart b/packages/pinball_components/test/helpers/test_game.dart index 1f8b9ee6..57c7961c 100644 --- a/packages/pinball_components/test/helpers/test_game.dart +++ b/packages/pinball_components/test/helpers/test_game.dart @@ -1,3 +1,4 @@ +import 'package:flame/game.dart'; import 'package:flame/input.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; @@ -20,3 +21,7 @@ class TestGame extends Forge2DGame { class KeyboardTestGame extends TestGame with HasKeyboardHandlerComponents { KeyboardTestGame([List? assets]) : super(assets); } + +class TappablesTestGame extends TestGame with HasTappables { + TappablesTestGame([List? assets]) : super(assets); +} diff --git a/packages/pinball_components/test/src/components/arrow_icon_test.dart b/packages/pinball_components/test/src/components/arrow_icon_test.dart new file mode 100644 index 00000000..39522d0d --- /dev/null +++ b/packages/pinball_components/test/src/components/arrow_icon_test.dart @@ -0,0 +1,96 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +abstract class _VoidCallbackStubBase { + void onCall(); +} + +class _VoidCallbackStub extends Mock implements _VoidCallbackStubBase {} + +void main() { + group('ArrowIcon', () { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + Assets.images.displayArrows.arrowLeft.keyName, + Assets.images.displayArrows.arrowRight.keyName, + ]; + final flameTester = FlameTester(() => TappablesTestGame(assets)); + + flameTester.testGameWidget( + 'is tappable', + setUp: (game, tester) async { + final stub = _VoidCallbackStub(); + await game.images.loadAll(assets); + await game.ensureAdd( + ArrowIcon( + position: Vector2.zero(), + direction: ArrowIconDirection.left, + onTap: stub.onCall, + ), + ); + await tester.pump(); + await tester.tapAt(Offset.zero); + await tester.pump(); + }, + verify: (game, tester) async { + final icon = game.descendants().whereType().single; + verify(icon.onTap).called(1); + }, + ); + + group('left', () { + flameTester.testGameWidget( + 'renders correctly', + setUp: (game, tester) async { + await game.images.loadAll(assets); + game.camera.followVector2(Vector2.zero()); + await game.add( + ArrowIcon( + position: Vector2.zero(), + direction: ArrowIconDirection.left, + onTap: () {}, + ), + ); + await tester.pump(); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/arrow_icon_left.png'), + ); + }, + ); + }); + + group('right', () { + flameTester.testGameWidget( + 'renders correctly', + setUp: (game, tester) async { + await game.images.loadAll(assets); + game.camera.followVector2(Vector2.zero()); + await game.add( + ArrowIcon( + position: Vector2.zero(), + direction: ArrowIconDirection.right, + onTap: () {}, + ), + ); + await tester.pump(); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/arrow_icon_right.png'), + ); + }, + ); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/golden/arrow_icon_left.png b/packages/pinball_components/test/src/components/golden/arrow_icon_left.png new file mode 100644 index 0000000000000000000000000000000000000000..dab87d412edf3dcbbaa5d7f284161baa4208fb90 GIT binary patch literal 22848 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 literal 0 HcmV?d00001 diff --git a/packages/pinball_components/test/src/components/golden/arrow_icon_right.png b/packages/pinball_components/test/src/components/golden/arrow_icon_right.png new file mode 100644 index 0000000000000000000000000000000000000000..185e9a9a58316bbee9c17f0b76a4af2a5b32de00 GIT binary patch literal 22823 zcmeHPc~n!`8GpEejx57WT@cx3)H7#Vbqp#Xiy|&`#0{QUHNa6O2ufH3fv`ld#SS{r zqemuyq#mtPA4=*35+OiBIT$TMMrmYX2#F12hBYt@1PM#u4T*U(EvL0T{iD-+|9LlW zx$oZJ{e8dhyWhL;+x>fY*)DQggdm7*@a~|42(mB=L9A_T7T{;@%f{#72dk8Wy8@Al zR_AW~$Gnul;4mA!WZ6WYLl8G4IA~iKy|nN7He1BqK`fp>U8eK=%HExQV80caG;E7M zGE*d@{pKpd3}61o*FQYwE-}})MvLc|>*p39Hdm2LR&SZ>ZX2Sk%=MrJKQ~vI51I4L zbqCnE843ssWQ&;#(1@V6n79BX7di_Q6`&7=p@oSGFyexNsEG_r9WB;7^i%)(}UbJd|-p7g7>B}7*J?Lku zqgE@smJZ%rh zd3CVSC5T07LM7!_R|SIJ5g!ssZ=9LGgMddw5U6p{$zMxMRPS(1$?U-3~O{ z@y*Z9f}^px&rBsfkD5?0{V0Yf|4W+iE{65?_BOB!3kx-O4;2;RFTVWf=CfWJnw_yl z_F~iRN72b?EDY;UW2CY28`HC?{+w`GfFR=+O9~sPGdU$IEIKgTgcH#t^5X_plijxpbbO5eKr^JqK?_&u^{vjHAtbLhnsTVG z<8ij?iGFgrQnEe$uFs^wKl%E-TtRmK^l=RNBkQENAE-dv^V$FLr0z_fFzoGQR^ zZ7N)vPvS<3{HG?gPoBDmWloNd$o()3w_mxR-IBs4_2alI48!E74~eFy4C=>CO*>Gu zj-jt83&@>ndL?1Gp5n>FP?dQPv*knqxtlH0Rukz+bj+!jm1F3?%&XUxV?C^ZDKFtJRE8HESgEkK*10rea-4>u_-HeS{)z*g<{IGaQ!*n9q-aqE+`t(R;tw0vm13AuI-f$WPbh0 zw+rqFrQRdw8!wQx6nq@rshMd&_LyHtY%ReI5g#I)nWJADzjh4vSH*7`1C%J&8F(ae zyV6ivuj|!FSVL0R9KO!L(4Q=R+2>Lq4<)6Gh6kt|T{2Zw(K|57=rv4@O&HPuSJd5! zglYS`dAKyAVZPf0g2rhr^~c8R#FC(>nM!eW5#gxp^d>`B-=I_dKeAv{mRx-Y%o)k) z0*1wjRIa&PAuh8y?SNVj;;MY6BD85kJ=*{n9M;Uc@j8OcMjRqAM4pxTjT*%~i&4@- zG18}}W}t6RJ58pal(TV|8Vl?tucT6!FC{Nxr2R>%6g!oSAFu4}v}<+F8XLTBv^Q<; z^rXH{q@xNLU#S*?egwE~wtT!gyA!2Ni53_|o=b9JSqF$1ABVPi4&m=vBht8&RLQKy zSJ@JLbIoW^;3M1N%|*!QK&^ z>=d4SO-zA0b_2Uqc>{|RD$A4_^&pw^WA``XOU|ILE%Wf*LnA=fN#K<^)QD|1paO~m z5u{m+k%*~KBbmHBPK+-ulwUGTejc2PUQ_ z4ROVk!Q}09E>rI)ZK`{<6(;Bkoe9c7o`QA)%Tfdg9AO zh79*EwN^O0_fZu9WG2_$D6r}RKJNSLBJ9grw8NUm$L}pGU4CIF9ucT?*6-cr$Frn> z*-Y*aybwWchy$Dj4`Sc`O!uuAqWjYMivG_?A#ICWE93Ez^W>)oYjp-q3h2N3R5c;? z_`dJnQgD`;QhduhgMqfw&=INa;kNEnd{lFvz1RmXj$NIR6Q=8P+$cse{pg!GQWmMO z>3AZ=(**t@N} z5_lpx@I<-P>Keuh;|JIYk&V%RuzKAz=R+`?V&cclu7Ce!v#IJVJb<^qsbWANA!i_G5J&<@0!V^4jyIwAfWiTV z0}2Ne4g`|m2PBZh7Og~(W!2#S0=z*sXrh3;#khQ3)|KE_plo^@tR>&K pump(Backbox component) { - return ensureAdd( + Future pump(Backbox component) async { + // Not needed once https://github.com/flame-engine/flame/issues/1607 + // is fixed + await onLoad(); + await ensureAdd( FlameBlocProvider.value( value: GameBloc(), children: [ diff --git a/test/game/components/backbox/displays/leaderboard_display_test.dart b/test/game/components/backbox/displays/leaderboard_display_test.dart index 263222fc..46fe6cdc 100644 --- a/test/game/components/backbox/displays/leaderboard_display_test.dart +++ b/test/game/components/backbox/displays/leaderboard_display_test.dart @@ -1,6 +1,7 @@ // ignore_for_file: cascade_invocations import 'package:flame/components.dart'; +import 'package:flame/game.dart'; import 'package:flame_forge2d/forge2d_game.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -8,8 +9,9 @@ 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_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; +import 'package:pinball_theme/pinball_theme.dart' hide Assets; class _MockAppLocalizations extends Mock implements AppLocalizations { @override @@ -22,12 +24,16 @@ class _MockAppLocalizations extends Mock implements AppLocalizations { String get name => 'name'; } -class _TestGame extends Forge2DGame { +class _TestGame extends Forge2DGame with HasTappables { @override Future onLoad() async { await super.onLoad(); images.prefix = ''; - await images.load(const AndroidTheme().leaderboardIcon.keyName); + await images.loadAll([ + const AndroidTheme().leaderboardIcon.keyName, + Assets.images.displayArrows.arrowLeft.keyName, + Assets.images.displayArrows.arrowRight.keyName, + ]); } Future pump(LeaderboardDisplay component) { @@ -40,6 +46,59 @@ class _TestGame extends Forge2DGame { } } +const leaderboard = [ + 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, + ), + LeaderboardEntryData( + playerInitials: 'EEE', + score: 123467, + character: CharacterType.android, + ), + LeaderboardEntryData( + playerInitials: 'FFF', + score: 123468, + character: CharacterType.android, + ), + LeaderboardEntryData( + playerInitials: 'GGG', + score: 1234689, + character: CharacterType.android, + ), + LeaderboardEntryData( + playerInitials: 'HHH', + score: 12346891, + character: CharacterType.android, + ), + LeaderboardEntryData( + playerInitials: 'III', + score: 123468912, + character: CharacterType.android, + ), + LeaderboardEntryData( + playerInitials: 'JJJ', + score: 1234689121, + character: CharacterType.android, + ), +]; + void main() { group('LeaderboardDisplay', () { TestWidgetsFlutterBinding.ensureInitialized(); @@ -57,43 +116,20 @@ void main() { 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, - ), - ], - ), - ); + flameTester.test('renders the first 5 entries', (game) async { + await game.pump(LeaderboardDisplay(entries: leaderboard)); for (final text in [ 'AAA', 'BBB', 'CCC', 'DDD', + 'EEE', '1st', '2nd', '3rd', - '4th' + '4th', + '5th', ]) { expect( game @@ -105,5 +141,120 @@ void main() { ); } }); + + flameTester.test('can open the second page', (game) async { + final display = LeaderboardDisplay(entries: leaderboard); + await game.pump(display); + + final arrow = game + .descendants() + .whereType() + .where((arrow) => arrow.direction == ArrowIconDirection.right) + .single; + + // Tap the arrow + arrow.onTap(); + // Wait for the transition to finish + display.updateTree(5); + await game.ready(); + + for (final text in [ + 'FFF', + 'GGG', + 'HHH', + 'III', + 'JJJ', + '6th', + '7th', + '8th', + '9th', + '10th', + ]) { + expect( + game + .descendants() + .whereType() + .where((textComponent) => textComponent.text == text) + .length, + equals(1), + ); + } + }); + + flameTester.test( + 'can open the second page and go back to the first', + (game) async { + final display = LeaderboardDisplay(entries: leaderboard); + await game.pump(display); + + var arrow = game + .descendants() + .whereType() + .where((arrow) => arrow.direction == ArrowIconDirection.right) + .single; + + // Tap the arrow + arrow.onTap(); + // Wait for the transition to finish + display.updateTree(5); + await game.ready(); + + for (final text in [ + 'FFF', + 'GGG', + 'HHH', + 'III', + 'JJJ', + '6th', + '7th', + '8th', + '9th', + '10th', + ]) { + expect( + game + .descendants() + .whereType() + .where((textComponent) => textComponent.text == text) + .length, + equals(1), + ); + } + + arrow = game + .descendants() + .whereType() + .where((arrow) => arrow.direction == ArrowIconDirection.left) + .single; + + // Tap the arrow + arrow.onTap(); + // Wait for the transition to finish + display.updateTree(5); + await game.ready(); + + for (final text in [ + 'AAA', + 'BBB', + 'CCC', + 'DDD', + 'EEE', + '1st', + '2nd', + '3rd', + '4th', + '5th', + ]) { + expect( + game + .descendants() + .whereType() + .where((textComponent) => textComponent.text == text) + .length, + equals(1), + ); + } + }, + ); }); } diff --git a/test/game/components/game_bloc_status_listener_test.dart b/test/game/components/game_bloc_status_listener_test.dart index cc1729b8..c1ca9b3e 100644 --- a/test/game/components/game_bloc_status_listener_test.dart +++ b/test/game/components/game_bloc_status_listener_test.dart @@ -1,6 +1,7 @@ // ignore_for_file: cascade_invocations import 'package:flame/components.dart'; +import 'package:flame/game.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; @@ -16,7 +17,7 @@ import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; import 'package:share_repository/share_repository.dart'; -class _TestGame extends Forge2DGame { +class _TestGame extends Forge2DGame with HasTappables { @override Future onLoad() async { images.prefix = ''; @@ -25,6 +26,8 @@ class _TestGame extends Forge2DGame { const theme.DashTheme().leaderboardIcon.keyName, Assets.images.backbox.marquee.keyName, Assets.images.backbox.displayDivider.keyName, + Assets.images.displayArrows.arrowLeft.keyName, + Assets.images.displayArrows.arrowRight.keyName, ], ); }