feat: added animations to character selection (#284)

pull/292/head
Jorge Coca 3 years ago committed by GitHub
parent 6f2344cbc9
commit 26acb63460
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -44,15 +44,14 @@ class PinballGamePage extends StatelessWidget {
...game.preLoadAssets(), ...game.preLoadAssets(),
pinballAudio.load(), pinballAudio.load(),
...BonusAnimation.loadAssets(), ...BonusAnimation.loadAssets(),
...SelectedCharacter.loadAssets(),
]; ];
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider(create: (_) => StartGameBloc(game: game)), BlocProvider(create: (_) => StartGameBloc(game: game)),
BlocProvider(create: (_) => GameBloc()), BlocProvider(create: (_) => GameBloc()),
BlocProvider( BlocProvider(create: (_) => AssetsManagerCubit(loadables)..load()),
create: (_) => AssetsManagerCubit(loadables)..load(),
),
], ],
child: PinballGameView(game: game), child: PinballGameView(game: game),
); );

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/how_to_play/how_to_play.dart'; import 'package:pinball/how_to_play/how_to_play.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/cubit/character_theme_cubit.dart';
import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_theme/pinball_theme.dart'; import 'package:pinball_theme/pinball_theme.dart';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
@ -118,19 +117,7 @@ class _CharacterPreview extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<CharacterThemeCubit, CharacterThemeState>( return BlocBuilder<CharacterThemeCubit, CharacterThemeState>(
builder: (context, state) { builder: (context, state) {
return Column( return SelectedCharacter(currentCharacter: state.characterTheme);
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
state.characterTheme.name,
style: Theme.of(context).textTheme.headline2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
Expanded(child: state.characterTheme.icon.image()),
],
);
}, },
); );
} }

@ -0,0 +1,102 @@
import 'package:flame/components.dart';
import 'package:flame/flame.dart';
import 'package:flame/sprite.dart';
import 'package:flutter/material.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
/// {@template selected_character}
/// Shows an animated version of the character currently selected.
/// {@endtemplate}
class SelectedCharacter extends StatefulWidget {
/// {@macro selected_character}
const SelectedCharacter({
Key? key,
required this.currentCharacter,
}) : super(key: key);
/// The character that is selected at the moment.
final CharacterTheme currentCharacter;
@override
State<SelectedCharacter> createState() => _SelectedCharacterState();
/// Returns a list of assets to be loaded.
static List<Future> loadAssets() {
return [
Flame.images.load(const DashTheme().animation.keyName),
Flame.images.load(const AndroidTheme().animation.keyName),
Flame.images.load(const DinoTheme().animation.keyName),
Flame.images.load(const SparkyTheme().animation.keyName),
];
}
}
class _SelectedCharacterState extends State<SelectedCharacter>
with TickerProviderStateMixin {
SpriteAnimationController? _controller;
@override
void initState() {
super.initState();
_setupCharacterAnimation();
}
@override
void didUpdateWidget(covariant SelectedCharacter oldWidget) {
super.didUpdateWidget(oldWidget);
_setupCharacterAnimation();
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
widget.currentCharacter.name,
style: Theme.of(context).textTheme.headline2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: SpriteAnimationWidget(
controller: _controller!,
anchor: Anchor.center,
),
);
},
),
),
],
);
}
void _setupCharacterAnimation() {
final spriteSheet = SpriteSheet.fromColumnsAndRows(
image: Flame.images.fromCache(widget.currentCharacter.animation.keyName),
columns: 12,
rows: 6,
);
final animation = spriteSheet.createAnimation(
row: 0,
stepTime: 1 / 24,
to: spriteSheet.rows * spriteSheet.columns,
);
if (_controller != null) _controller?.dispose();
_controller = SpriteAnimationController(vsync: this, animation: animation)
..forward()
..repeat();
}
}

@ -1 +1,2 @@
export 'character_selection_page.dart'; export 'character_selection_page.dart';
export 'selected_character.dart';

@ -401,13 +401,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
mockingjay:
dependency: "direct dev"
description:
name: mockingjay
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
mocktail: mocktail:
dependency: "direct dev" dependency: "direct dev"
description: description:

@ -49,7 +49,6 @@ dev_dependencies:
flame_test: ^1.3.0 flame_test: ^1.3.0
flutter_test: flutter_test:
sdk: flutter sdk: flutter
mockingjay: ^0.3.0
mocktail: ^0.3.0 mocktail: ^0.3.0
very_good_analysis: ^2.4.0 very_good_analysis: ^2.4.0

@ -3,7 +3,7 @@
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.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:mockingjay/mockingjay.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/components/google_word/behaviors/behaviors.dart'; import 'package:pinball/game/components/google_word/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';

@ -5,7 +5,7 @@ import 'dart:async';
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.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:mockingjay/mockingjay.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/components/multipliers/behaviors/behaviors.dart'; import 'package:pinball/game/components/multipliers/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';

@ -1,8 +1,10 @@
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/flame.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_theme/pinball_theme.dart';
import '../../../helpers/helpers.dart'; import '../../../helpers/helpers.dart';
@ -12,7 +14,12 @@ void main() {
late GameFlowController gameFlowController; late GameFlowController gameFlowController;
late CharacterThemeCubit characterThemeCubit; late CharacterThemeCubit characterThemeCubit;
setUp(() { setUp(() async {
Flame.images.prefix = '';
await Flame.images.load(const DashTheme().animation.keyName);
await Flame.images.load(const AndroidTheme().animation.keyName);
await Flame.images.load(const DinoTheme().animation.keyName);
await Flame.images.load(const SparkyTheme().animation.keyName);
game = MockPinballGame(); game = MockPinballGame();
gameFlowController = MockGameFlowController(); gameFlowController = MockGameFlowController();
characterThemeCubit = MockCharacterThemeCubit(); characterThemeCubit = MockCharacterThemeCubit();
@ -49,7 +56,7 @@ void main() {
characterThemeCubit: characterThemeCubit, characterThemeCubit: characterThemeCubit,
); );
await tester.tap(find.text('Play')); await tester.tap(find.text('Play'));
await tester.pumpAndSettle(); await tester.pump();
expect(find.byType(CharacterSelectionDialog), findsOneWidget); expect(find.byType(CharacterSelectionDialog), findsOneWidget);
}); });
}); });

@ -9,7 +9,6 @@ export 'fakes.dart';
export 'forge2d.dart'; export 'forge2d.dart';
export 'key_testers.dart'; export 'key_testers.dart';
export 'mocks.dart'; export 'mocks.dart';
export 'navigator.dart';
export 'pump_app.dart'; export 'pump_app.dart';
export 'test_games.dart'; export 'test_games.dart';
export 'text_span.dart'; export 'text_span.dart';

@ -1,37 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'helpers.dart';
Future<void> expectNavigatesToRoute<Type>(
WidgetTester tester,
Route route, {
bool hasFlameGameInside = false,
}) async {
// ignore: avoid_dynamic_calls
await tester.pumpApp(
Scaffold(
body: Builder(
builder: (context) {
return ElevatedButton(
onPressed: () {
Navigator.of(context).push<void>(route);
},
child: const Text('Tap me'),
);
},
),
),
);
await tester.tap(find.text('Tap me'));
if (hasFlameGameInside) {
// We can't use pumpAndSettle here because the page renders a Flame game
// which is an infinity animation, so it will timeout
await tester.pump(); // Runs the button action
await tester.pump(); // Runs the navigation
} else {
await tester.pumpAndSettle();
}
expect(find.byType(Type), findsOneWidget);
}

@ -11,7 +11,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mockingjay/mockingjay.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/select_character/select_character.dart';
@ -51,7 +51,6 @@ MockAssetsManagerCubit _buildDefaultAssetsManagerCubit() {
extension PumpApp on WidgetTester { extension PumpApp on WidgetTester {
Future<void> pumpApp( Future<void> pumpApp(
Widget widget, { Widget widget, {
MockNavigator? navigator,
GameBloc? gameBloc, GameBloc? gameBloc,
StartGameBloc? startGameBloc, StartGameBloc? startGameBloc,
AssetsManagerCubit? assetsManagerCubit, AssetsManagerCubit? assetsManagerCubit,
@ -92,9 +91,7 @@ extension PumpApp on WidgetTester {
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,
], ],
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
home: navigator != null home: widget,
? MockNavigatorProvider(navigator: navigator, child: widget)
: widget,
), ),
), ),
), ),

@ -1,4 +1,5 @@
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/flame.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@ -10,9 +11,15 @@ import 'package:pinball_ui/pinball_ui.dart';
import '../../helpers/helpers.dart'; import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late CharacterThemeCubit characterThemeCubit; late CharacterThemeCubit characterThemeCubit;
setUp(() { setUp(() async {
Flame.images.prefix = '';
await Flame.images.load(const DashTheme().animation.keyName);
await Flame.images.load(const AndroidTheme().animation.keyName);
await Flame.images.load(const DinoTheme().animation.keyName);
await Flame.images.load(const SparkyTheme().animation.keyName);
characterThemeCubit = MockCharacterThemeCubit(); characterThemeCubit = MockCharacterThemeCubit();
whenListen( whenListen(
characterThemeCubit, characterThemeCubit,
@ -38,7 +45,7 @@ void main() {
characterThemeCubit: characterThemeCubit, characterThemeCubit: characterThemeCubit,
); );
await tester.tap(find.text('test')); await tester.tap(find.text('test'));
await tester.pumpAndSettle(); await tester.pump();
expect(find.byType(CharacterSelectionDialog), findsOneWidget); expect(find.byType(CharacterSelectionDialog), findsOneWidget);
}); });
}); });
@ -50,7 +57,7 @@ void main() {
characterThemeCubit: characterThemeCubit, characterThemeCubit: characterThemeCubit,
); );
await tester.tap(find.byKey(const Key('sparky_character_selection'))); await tester.tap(find.byKey(const Key('sparky_character_selection')));
await tester.pumpAndSettle(); await tester.pump();
verify( verify(
() => characterThemeCubit.characterSelected(const SparkyTheme()), () => characterThemeCubit.characterSelected(const SparkyTheme()),
).called(1); ).called(1);
@ -68,5 +75,47 @@ void main() {
expect(find.byType(CharacterSelectionDialog), findsNothing); expect(find.byType(CharacterSelectionDialog), findsNothing);
expect(find.byType(HowToPlayDialog), findsOneWidget); expect(find.byType(HowToPlayDialog), findsOneWidget);
}); });
testWidgets('updating the selected character updates the preview',
(tester) async {
await tester.pumpApp(_TestCharacterPreview());
expect(find.text('Dash'), findsOneWidget);
await tester.tap(find.text('test'));
await tester.pump();
expect(find.text('Android'), findsOneWidget);
});
}); });
} }
class _TestCharacterPreview extends StatefulWidget {
@override
State<StatefulWidget> createState() => _TestCharacterPreviewState();
}
class _TestCharacterPreviewState extends State<_TestCharacterPreview> {
late CharacterTheme currentCharacter;
@override
void initState() {
super.initState();
currentCharacter = const DashTheme();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: SelectedCharacter(currentCharacter: currentCharacter)),
TextButton(
onPressed: () {
setState(() {
currentCharacter = const AndroidTheme();
});
},
child: const Text('test'),
)
],
);
}
}

Loading…
Cancel
Save