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(),
pinballAudio.load(),
...BonusAnimation.loadAssets(),
...SelectedCharacter.loadAssets(),
];
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => StartGameBloc(game: game)),
BlocProvider(create: (_) => GameBloc()),
BlocProvider(
create: (_) => AssetsManagerCubit(loadables)..load(),
),
BlocProvider(create: (_) => AssetsManagerCubit(loadables)..load()),
],
child: PinballGameView(game: game),
);

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/how_to_play/how_to_play.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_theme/pinball_theme.dart';
import 'package:pinball_ui/pinball_ui.dart';
@ -118,19 +117,7 @@ class _CharacterPreview extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<CharacterThemeCubit, CharacterThemeState>(
builder: (context, state) {
return Column(
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()),
],
);
return SelectedCharacter(currentCharacter: state.characterTheme);
},
);
}

@ -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 'selected_character.dart';

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

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

@ -3,7 +3,7 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_test/flame_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/game.dart';
import 'package:pinball_components/pinball_components.dart';

@ -5,7 +5,7 @@ import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_test/flame_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/game.dart';
import 'package:pinball_components/pinball_components.dart';

@ -1,8 +1,10 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/flame.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_theme/pinball_theme.dart';
import '../../../helpers/helpers.dart';
@ -12,7 +14,12 @@ void main() {
late GameFlowController gameFlowController;
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();
gameFlowController = MockGameFlowController();
characterThemeCubit = MockCharacterThemeCubit();
@ -49,7 +56,7 @@ void main() {
characterThemeCubit: characterThemeCubit,
);
await tester.tap(find.text('Play'));
await tester.pumpAndSettle();
await tester.pump();
expect(find.byType(CharacterSelectionDialog), findsOneWidget);
});
});

@ -9,7 +9,6 @@ export 'fakes.dart';
export 'forge2d.dart';
export 'key_testers.dart';
export 'mocks.dart';
export 'navigator.dart';
export 'pump_app.dart';
export 'test_games.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_test/flutter_test.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/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart';
@ -51,7 +51,6 @@ MockAssetsManagerCubit _buildDefaultAssetsManagerCubit() {
extension PumpApp on WidgetTester {
Future<void> pumpApp(
Widget widget, {
MockNavigator? navigator,
GameBloc? gameBloc,
StartGameBloc? startGameBloc,
AssetsManagerCubit? assetsManagerCubit,
@ -92,9 +91,7 @@ extension PumpApp on WidgetTester {
GlobalMaterialLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
home: navigator != null
? MockNavigatorProvider(navigator: navigator, child: widget)
: widget,
home: widget,
),
),
),

@ -1,4 +1,5 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/flame.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
@ -10,9 +11,15 @@ import 'package:pinball_ui/pinball_ui.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
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();
whenListen(
characterThemeCubit,
@ -38,7 +45,7 @@ void main() {
characterThemeCubit: characterThemeCubit,
);
await tester.tap(find.text('test'));
await tester.pumpAndSettle();
await tester.pump();
expect(find.byType(CharacterSelectionDialog), findsOneWidget);
});
});
@ -50,7 +57,7 @@ void main() {
characterThemeCubit: characterThemeCubit,
);
await tester.tap(find.byKey(const Key('sparky_character_selection')));
await tester.pumpAndSettle();
await tester.pump();
verify(
() => characterThemeCubit.characterSelected(const SparkyTheme()),
).called(1);
@ -68,5 +75,47 @@ void main() {
expect(find.byType(CharacterSelectionDialog), findsNothing);
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