Merge branch 'feat/select_character_widgets' into feat/final-select-character-dialog

pull/254/head
arturplaczek 3 years ago
commit 671d236a7e

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

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 'game_hud.dart';
export 'pinball_button.dart';
export 'play_button_overlay.dart';
export 'round_count_display.dart';
export 'score_view.dart';

@ -15,6 +15,8 @@ class $AssetsImagesGen {
$AssetsImagesComponentsGen get components =>
const $AssetsImagesComponentsGen();
$AssetsImagesScoreGen get score => const $AssetsImagesScoreGen();
$AssetsImagesSelectCharacterGen get selectCharacter =>
const $AssetsImagesSelectCharacterGen();
}
class $AssetsImagesBonusAnimationGen {
@ -57,6 +59,26 @@ class $AssetsImagesScoreGen {
const AssetGenImage('assets/images/score/mini_score_background.png');
}
class $AssetsImagesSelectCharacterGen {
const $AssetsImagesSelectCharacterGen();
/// File path: assets/images/select_character/pinball_button.png
AssetGenImage get pinballButton =>
const AssetGenImage('assets/images/select_character/pinball_button.png');
/// File path: assets/images/select_character/star_a.png
AssetGenImage get starA =>
const AssetGenImage('assets/images/select_character/star_a.png');
/// File path: assets/images/select_character/star_b.png
AssetGenImage get starB =>
const AssetGenImage('assets/images/select_character/star_b.png');
/// File path: assets/images/select_character/star_c.png
AssetGenImage get starC =>
const AssetGenImage('assets/images/select_character/star_c.png');
}
class Assets {
Assets._();

@ -1,2 +1,3 @@
export 'cubit/character_theme_cubit.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';

@ -55,6 +55,7 @@ flutter:
- assets/images/components/
- assets/images/bonus_animation/
- assets/images/score/
- assets/images/select_character/
flutter_gen:
line_length: 80

@ -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…
Cancel
Save