mirror of https://github.com/flutter/pinball.git
commit
671d236a7e
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 484 B |
@ -0,0 +1,46 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball/gen/gen.dart';
|
||||||
|
|
||||||
|
// TODO(arturplaczek): move PinballButton to pinball_ui
|
||||||
|
|
||||||
|
/// {@template pinball_button}
|
||||||
|
/// Pinball button with onPressed [VoidCallback] and child [Widget].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class PinballButton extends StatelessWidget {
|
||||||
|
/// {@macro pinball_button}
|
||||||
|
const PinballButton({
|
||||||
|
Key? key,
|
||||||
|
required Widget child,
|
||||||
|
VoidCallback? onPressed,
|
||||||
|
}) : _child = child,
|
||||||
|
_onPressed = onPressed,
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
final Widget _child;
|
||||||
|
final VoidCallback? _onPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: _onPressed,
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage(
|
||||||
|
Assets.images.selectCharacter.pinballButton.keyName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 32,
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
|
child: _child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
export 'bonus_animation.dart';
|
export 'bonus_animation.dart';
|
||||||
export 'game_hud.dart';
|
export 'game_hud.dart';
|
||||||
|
export 'pinball_button.dart';
|
||||||
export 'play_button_overlay.dart';
|
export 'play_button_overlay.dart';
|
||||||
export 'round_count_display.dart';
|
export 'round_count_display.dart';
|
||||||
export 'score_view.dart';
|
export 'score_view.dart';
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export 'cubit/character_theme_cubit.dart';
|
export 'cubit/character_theme_cubit.dart';
|
||||||
export 'view/view.dart';
|
export 'view/view.dart';
|
||||||
|
export 'widgets/widgets.dart';
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:pinball/select_character/select_character.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart' hide Assets;
|
||||||
|
|
||||||
|
/// {@template character_icon}
|
||||||
|
/// Widget to display character icon.
|
||||||
|
///
|
||||||
|
/// On tap changes selected character in [CharacterThemeCubit].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class CharacterIcon extends StatelessWidget {
|
||||||
|
/// {@macro character_icon}
|
||||||
|
const CharacterIcon(
|
||||||
|
CharacterTheme characterTheme, {
|
||||||
|
Key? key,
|
||||||
|
}) : _characterTheme = characterTheme,
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
final CharacterTheme _characterTheme;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final currentCharacterTheme =
|
||||||
|
context.select<CharacterThemeCubit, CharacterTheme>(
|
||||||
|
(cubit) => cubit.state.characterTheme,
|
||||||
|
);
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => context
|
||||||
|
.read<CharacterThemeCubit>()
|
||||||
|
.characterSelected(_characterTheme),
|
||||||
|
child: Opacity(
|
||||||
|
opacity: currentCharacterTheme == _characterTheme ? 1 : 0.5,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: _characterTheme.icon.image(
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
import 'package:flame/flame.dart';
|
||||||
|
import 'package:flame/sprite.dart';
|
||||||
|
import 'package:flutter/material.dart' hide Image;
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:pinball/select_character/select_character.dart';
|
||||||
|
import 'package:pinball/theme/theme.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
/// {@template selected_character}
|
||||||
|
/// Widget to display the selected character based on the [CharacterThemeCubit]
|
||||||
|
/// state.
|
||||||
|
///
|
||||||
|
/// Displays the looped [SpriteAnimationWidget] and the character name on the
|
||||||
|
/// list.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class SelectedCharacter extends StatefulWidget {
|
||||||
|
/// {@macro selected_character}
|
||||||
|
const SelectedCharacter({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SelectedCharacter> createState() => _SelectedCharacterState();
|
||||||
|
|
||||||
|
/// Returns a list of assets to be loaded.
|
||||||
|
static List<Future> loadAssets() {
|
||||||
|
Flame.images.prefix = '';
|
||||||
|
|
||||||
|
const dashTheme = DashTheme();
|
||||||
|
const androidTheme = AndroidTheme();
|
||||||
|
const dinoTheme = DinoTheme();
|
||||||
|
const sparkyTheme = SparkyTheme();
|
||||||
|
|
||||||
|
return [
|
||||||
|
Flame.images.load(dashTheme.animation.keyName),
|
||||||
|
Flame.images.load(androidTheme.animation.keyName),
|
||||||
|
Flame.images.load(dinoTheme.animation.keyName),
|
||||||
|
Flame.images.load(sparkyTheme.animation.keyName),
|
||||||
|
Flame.images.load(dashTheme.background.keyName),
|
||||||
|
Flame.images.load(androidTheme.background.keyName),
|
||||||
|
Flame.images.load(dinoTheme.background.keyName),
|
||||||
|
Flame.images.load(sparkyTheme.background.keyName),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SelectedCharacterState extends State<SelectedCharacter>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
late SpriteAnimationController _controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final currentCharacter =
|
||||||
|
context.select<CharacterThemeCubit, CharacterTheme>(
|
||||||
|
(cubit) => cubit.state.characterTheme,
|
||||||
|
);
|
||||||
|
final spriteSheet = SpriteSheet.fromColumnsAndRows(
|
||||||
|
image: Flame.images.fromCache(currentCharacter.animation.keyName),
|
||||||
|
columns: 12,
|
||||||
|
rows: 6,
|
||||||
|
);
|
||||||
|
final animation = spriteSheet.createAnimation(
|
||||||
|
row: 0,
|
||||||
|
stepTime: 1 / 24,
|
||||||
|
to: spriteSheet.rows * spriteSheet.columns,
|
||||||
|
);
|
||||||
|
|
||||||
|
_controller = SpriteAnimationController(
|
||||||
|
vsync: this,
|
||||||
|
animation: animation,
|
||||||
|
);
|
||||||
|
|
||||||
|
_controller
|
||||||
|
..forward()
|
||||||
|
..repeat();
|
||||||
|
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return ListView(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
currentCharacter.name,
|
||||||
|
style: AppTextStyle.headline3,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
SizedBox(
|
||||||
|
width: constraints.maxWidth,
|
||||||
|
height: constraints.maxWidth,
|
||||||
|
child: SpriteAnimationWidget(
|
||||||
|
controller: _controller,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
import 'package:flame/flame.dart';
|
||||||
|
import 'package:flame/sprite.dart';
|
||||||
|
import 'package:flame/widgets.dart';
|
||||||
|
import 'package:flutter/material.dart' hide Image;
|
||||||
|
import 'package:pinball/gen/gen.dart';
|
||||||
|
|
||||||
|
/// {@template star_animation}
|
||||||
|
/// Widget to display a looped the star animation.
|
||||||
|
///
|
||||||
|
/// For animation uses [SpriteAnimationWidget].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class StarAnimation extends StatelessWidget {
|
||||||
|
const StarAnimation._({
|
||||||
|
Key? key,
|
||||||
|
required String imagePath,
|
||||||
|
required int columns,
|
||||||
|
required int rows,
|
||||||
|
required double stepTime,
|
||||||
|
}) : _imagePath = imagePath,
|
||||||
|
_columns = columns,
|
||||||
|
_rows = rows,
|
||||||
|
_stepTime = stepTime,
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
/// [Widget] that displays the star A animation.
|
||||||
|
StarAnimation.starA({
|
||||||
|
Key? key,
|
||||||
|
}) : this._(
|
||||||
|
key: key,
|
||||||
|
imagePath: Assets.images.selectCharacter.starA.keyName,
|
||||||
|
columns: 36,
|
||||||
|
rows: 2,
|
||||||
|
stepTime: 1 / 18,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// [Widget] that displays the star B animation.
|
||||||
|
StarAnimation.starB({
|
||||||
|
Key? key,
|
||||||
|
}) : this._(
|
||||||
|
key: key,
|
||||||
|
imagePath: Assets.images.selectCharacter.starB.keyName,
|
||||||
|
columns: 36,
|
||||||
|
rows: 2,
|
||||||
|
stepTime: 1 / 36,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// [Widget] that displays the star C animation.
|
||||||
|
StarAnimation.starC({
|
||||||
|
Key? key,
|
||||||
|
}) : this._(
|
||||||
|
key: key,
|
||||||
|
imagePath: Assets.images.selectCharacter.starC.keyName,
|
||||||
|
columns: 72,
|
||||||
|
rows: 1,
|
||||||
|
stepTime: 1 / 24,
|
||||||
|
);
|
||||||
|
|
||||||
|
final String _imagePath;
|
||||||
|
final int _columns;
|
||||||
|
final int _rows;
|
||||||
|
final double _stepTime;
|
||||||
|
|
||||||
|
/// Returns a list of assets to be loaded.
|
||||||
|
static Future<void> loadAssets() {
|
||||||
|
Flame.images.prefix = '';
|
||||||
|
|
||||||
|
return Flame.images.loadAll([
|
||||||
|
Assets.images.selectCharacter.starA.keyName,
|
||||||
|
Assets.images.selectCharacter.starB.keyName,
|
||||||
|
Assets.images.selectCharacter.starC.keyName,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final spriteSheet = SpriteSheet.fromColumnsAndRows(
|
||||||
|
image: Flame.images.fromCache(_imagePath),
|
||||||
|
columns: _columns,
|
||||||
|
rows: _rows,
|
||||||
|
);
|
||||||
|
final animation = spriteSheet.createAnimation(
|
||||||
|
row: 0,
|
||||||
|
stepTime: _stepTime,
|
||||||
|
to: spriteSheet.rows * spriteSheet.columns,
|
||||||
|
);
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
child: SpriteAnimationWidget(
|
||||||
|
animation: animation,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
export 'character_icon.dart';
|
||||||
|
export 'selected_character.dart';
|
||||||
|
export 'star_animation.dart';
|
@ -0,0 +1,39 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
|
||||||
|
import '../../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
const buttonText = 'this is the button text';
|
||||||
|
|
||||||
|
testWidgets('displays button', (tester) async {
|
||||||
|
await tester.pumpApp(
|
||||||
|
const Material(
|
||||||
|
child: PinballButton(
|
||||||
|
child: Text(buttonText),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text(buttonText), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('on tap calls onPressed callback', (tester) async {
|
||||||
|
var isTapped = false;
|
||||||
|
|
||||||
|
await tester.pumpApp(
|
||||||
|
Material(
|
||||||
|
child: PinballButton(
|
||||||
|
child: const Text(buttonText),
|
||||||
|
onPressed: () {
|
||||||
|
isTapped = true;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.tap(find.text(buttonText));
|
||||||
|
|
||||||
|
expect(isTapped, isTrue);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mockingjay/mockingjay.dart';
|
||||||
|
import 'package:pinball/select_character/select_character.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late CharacterThemeCubit characterThemeCubit;
|
||||||
|
|
||||||
|
group('CharacterIcon', () {
|
||||||
|
setUp(() {
|
||||||
|
characterThemeCubit = MockCharacterThemeCubit();
|
||||||
|
|
||||||
|
whenListen(
|
||||||
|
characterThemeCubit,
|
||||||
|
const Stream<CharacterThemeState>.empty(),
|
||||||
|
initialState: const CharacterThemeState.initial(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('renders character icon', (tester) async {
|
||||||
|
const characterTheme = DashTheme();
|
||||||
|
|
||||||
|
await tester.pumpApp(
|
||||||
|
const CharacterIcon(characterTheme),
|
||||||
|
characterThemeCubit: characterThemeCubit,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.image(characterTheme.icon), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('tap on icon calls characterSelected on cubit', (tester) async {
|
||||||
|
const characterTheme = DashTheme();
|
||||||
|
|
||||||
|
await tester.pumpApp(
|
||||||
|
CharacterIcon(characterTheme),
|
||||||
|
characterThemeCubit: characterThemeCubit,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byType(CharacterIcon));
|
||||||
|
|
||||||
|
verify(
|
||||||
|
() => characterThemeCubit.characterSelected(characterTheme),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flame/flame.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball/select_character/select_character.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
late CharacterThemeCubit characterThemeCubit;
|
||||||
|
|
||||||
|
setUpAll(() async {
|
||||||
|
Flame.images.prefix = '';
|
||||||
|
await Flame.images.load(const DashTheme().animation.keyName);
|
||||||
|
});
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
characterThemeCubit = MockCharacterThemeCubit();
|
||||||
|
|
||||||
|
whenListen(
|
||||||
|
characterThemeCubit,
|
||||||
|
Stream.value(const CharacterThemeState.initial()),
|
||||||
|
initialState: const CharacterThemeState.initial(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('SelectedCharacter', () {
|
||||||
|
testWidgets('loadAssets method returns list of futures', (tester) async {
|
||||||
|
expect(SelectedCharacter.loadAssets(), isList);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('renders selected character', (tester) async {
|
||||||
|
await tester.pumpApp(
|
||||||
|
SelectedCharacter(),
|
||||||
|
characterThemeCubit: characterThemeCubit,
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byType(SpriteAnimationWidget), findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
// ignore_for_file: invalid_use_of_protected_member
|
||||||
|
|
||||||
|
import 'package:flame/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball/select_character/select_character.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('loads SpriteAnimationWidget correctly for', () {
|
||||||
|
setUpAll(() async {
|
||||||
|
await StarAnimation.loadAssets();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('starA', (tester) async {
|
||||||
|
await tester.pumpApp(
|
||||||
|
StarAnimation.starA(),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byType(SpriteAnimationWidget), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('starB', (tester) async {
|
||||||
|
await tester.pumpApp(
|
||||||
|
StarAnimation.starB(),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byType(SpriteAnimationWidget), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('starC', (tester) async {
|
||||||
|
await tester.pumpApp(
|
||||||
|
StarAnimation.starC(),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byType(SpriteAnimationWidget), findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in new issue