@ -1,8 +1,8 @@
|
||||
export 'ball_spawning_behavior.dart';
|
||||
export 'ball_theming_behavior.dart';
|
||||
export 'bonus_ball_spawning_behavior.dart';
|
||||
export 'bonus_noise_behavior.dart';
|
||||
export 'bumper_noise_behavior.dart';
|
||||
export 'camera_focusing_behavior.dart';
|
||||
export 'character_selection_behavior.dart';
|
||||
export 'cow_bumper_noise_behavior.dart';
|
||||
export 'scoring_behavior.dart';
|
||||
|
@ -0,0 +1,189 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball/l10n/l10n.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
import 'package:pinball_ui/pinball_ui.dart';
|
||||
import 'package:share_repository/share_repository.dart';
|
||||
|
||||
/// Signature for the callback called when the user tries to share their score
|
||||
/// on the [ShareDisplay].
|
||||
typedef OnSocialShareTap = void Function(SharePlatform);
|
||||
|
||||
final _descriptionTextPaint = TextPaint(
|
||||
style: const TextStyle(
|
||||
fontSize: 1.6,
|
||||
color: PinballColors.white,
|
||||
fontFamily: PinballFonts.pixeloidSans,
|
||||
),
|
||||
);
|
||||
|
||||
/// {@template share_display}
|
||||
/// Display that allows users to share their score to social networks.
|
||||
/// {@endtemplate}
|
||||
class ShareDisplay extends Component with HasGameRef {
|
||||
/// {@macro share_display}
|
||||
ShareDisplay({
|
||||
OnSocialShareTap? onShare,
|
||||
}) : super(
|
||||
children: [
|
||||
_ShareInstructionsComponent(
|
||||
onShare: onShare,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class _ShareInstructionsComponent extends PositionComponent with HasGameRef {
|
||||
_ShareInstructionsComponent({
|
||||
OnSocialShareTap? onShare,
|
||||
}) : super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2(0, -25),
|
||||
children: [
|
||||
_DescriptionComponent(),
|
||||
_SocialNetworksComponent(
|
||||
onShare: onShare,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class _DescriptionComponent extends PositionComponent with HasGameRef {
|
||||
_DescriptionComponent()
|
||||
: super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2.zero(),
|
||||
children: [
|
||||
_LetEveryoneTextComponent(),
|
||||
_SharingYourScoreTextComponent(),
|
||||
_SocialMediaTextComponent(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class _LetEveryoneTextComponent extends TextComponent with HasGameRef {
|
||||
_LetEveryoneTextComponent()
|
||||
: super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2.zero(),
|
||||
textRenderer: _descriptionTextPaint,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
text = readProvider<AppLocalizations>().letEveryone;
|
||||
}
|
||||
}
|
||||
|
||||
class _SharingYourScoreTextComponent extends TextComponent with HasGameRef {
|
||||
_SharingYourScoreTextComponent()
|
||||
: super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2(0, 2.5),
|
||||
textRenderer: _descriptionTextPaint,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
text = readProvider<AppLocalizations>().bySharingYourScore;
|
||||
}
|
||||
}
|
||||
|
||||
class _SocialMediaTextComponent extends TextComponent with HasGameRef {
|
||||
_SocialMediaTextComponent()
|
||||
: super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2(0, 5),
|
||||
textRenderer: _descriptionTextPaint,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
text = readProvider<AppLocalizations>().socialMediaAccount;
|
||||
}
|
||||
}
|
||||
|
||||
class _SocialNetworksComponent extends PositionComponent with HasGameRef {
|
||||
_SocialNetworksComponent({
|
||||
OnSocialShareTap? onShare,
|
||||
}) : super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2(0, 12),
|
||||
children: [
|
||||
FacebookButtonComponent(onTap: onShare),
|
||||
TwitterButtonComponent(onTap: onShare),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// {@template facebook_button_component}
|
||||
/// Button for sharing on Facebook.
|
||||
/// {@endtemplate}
|
||||
class FacebookButtonComponent extends SpriteComponent
|
||||
with HasGameRef, Tappable {
|
||||
/// {@macro facebook_button_component}
|
||||
FacebookButtonComponent({
|
||||
OnSocialShareTap? onTap,
|
||||
}) : _onTap = onTap,
|
||||
super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2(-5, 0),
|
||||
);
|
||||
|
||||
final OnSocialShareTap? _onTap;
|
||||
|
||||
@override
|
||||
bool onTapDown(TapDownInfo info) {
|
||||
_onTap?.call(SharePlatform.facebook);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
final sprite = Sprite(
|
||||
gameRef.images.fromCache(Assets.images.backbox.button.facebook.keyName),
|
||||
);
|
||||
this.sprite = sprite;
|
||||
size = sprite.originalSize / 25;
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template twitter_button_component}
|
||||
/// Button for sharing on Twitter.
|
||||
/// {@endtemplate}
|
||||
class TwitterButtonComponent extends SpriteComponent with HasGameRef, Tappable {
|
||||
/// {@macro twitter_button_component}
|
||||
TwitterButtonComponent({
|
||||
OnSocialShareTap? onTap,
|
||||
}) : _onTap = onTap,
|
||||
super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2(5, 0),
|
||||
);
|
||||
|
||||
final OnSocialShareTap? _onTap;
|
||||
|
||||
@override
|
||||
bool onTapDown(TapDownInfo info) {
|
||||
_onTap?.call(SharePlatform.twitter);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
final sprite = Sprite(
|
||||
gameRef.images.fromCache(Assets.images.backbox.button.twitter.keyName),
|
||||
);
|
||||
this.sprite = sprite;
|
||||
size = sprite.originalSize / 25;
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.5 KiB |
@ -0,0 +1,92 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_bloc/flame_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
import 'package:pinball_theme/pinball_theme.dart' as theme;
|
||||
|
||||
export 'cubit/arcade_background_cubit.dart';
|
||||
|
||||
/// {@template arcade_background}
|
||||
/// Background of the arcade that the pinball machine lives in.
|
||||
/// {@endtemplate}
|
||||
class ArcadeBackground extends Component with ZIndex {
|
||||
/// {@macro arcade_background}
|
||||
ArcadeBackground({String? assetPath})
|
||||
: this._(
|
||||
bloc: ArcadeBackgroundCubit(),
|
||||
assetPath: assetPath,
|
||||
);
|
||||
|
||||
ArcadeBackground._({required this.bloc, String? assetPath})
|
||||
: super(
|
||||
children: [
|
||||
FlameBlocProvider<ArcadeBackgroundCubit,
|
||||
ArcadeBackgroundState>.value(
|
||||
value: bloc,
|
||||
children: [ArcadeBackgroundSpriteComponent(assetPath: assetPath)],
|
||||
)
|
||||
],
|
||||
) {
|
||||
zIndex = ZIndexes.arcadeBackground;
|
||||
}
|
||||
|
||||
/// Creates an [ArcadeBackground] without any behaviors.
|
||||
///
|
||||
/// This can be used for testing [ArcadeBackground]'s behaviors in isolation.
|
||||
@visibleForTesting
|
||||
ArcadeBackground.test({
|
||||
ArcadeBackgroundCubit? bloc,
|
||||
String? assetPath,
|
||||
}) : bloc = bloc ?? ArcadeBackgroundCubit(),
|
||||
super(
|
||||
children: [
|
||||
FlameBlocProvider<ArcadeBackgroundCubit,
|
||||
ArcadeBackgroundState>.value(
|
||||
value: bloc ?? ArcadeBackgroundCubit(),
|
||||
children: [ArcadeBackgroundSpriteComponent(assetPath: assetPath)],
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
/// Bloc to update the arcade background sprite when a new character is
|
||||
/// selected.
|
||||
final ArcadeBackgroundCubit bloc;
|
||||
}
|
||||
|
||||
/// {@template arcade_background_sprite_component}
|
||||
/// [SpriteComponent] for the [ArcadeBackground].
|
||||
/// {@endtemplate}
|
||||
@visibleForTesting
|
||||
class ArcadeBackgroundSpriteComponent extends SpriteComponent
|
||||
with
|
||||
FlameBlocListenable<ArcadeBackgroundCubit, ArcadeBackgroundState>,
|
||||
HasGameRef {
|
||||
/// {@macro arcade_background_sprite_component}
|
||||
ArcadeBackgroundSpriteComponent({required String? assetPath})
|
||||
: _assetPath = assetPath,
|
||||
super(
|
||||
anchor: Anchor.bottomCenter,
|
||||
position: Vector2(0, 72.3),
|
||||
);
|
||||
|
||||
final String? _assetPath;
|
||||
|
||||
@override
|
||||
void onNewState(ArcadeBackgroundState state) {
|
||||
sprite = Sprite(
|
||||
gameRef.images.fromCache(state.characterTheme.background.keyName),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
final sprite = Sprite(
|
||||
gameRef.images
|
||||
.fromCache(_assetPath ?? theme.Assets.images.dash.background.keyName),
|
||||
);
|
||||
this.sprite = sprite;
|
||||
size = sprite.originalSize / 10;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:pinball_theme/pinball_theme.dart';
|
||||
|
||||
part 'arcade_background_state.dart';
|
||||
|
||||
class ArcadeBackgroundCubit extends Cubit<ArcadeBackgroundState> {
|
||||
ArcadeBackgroundCubit() : super(const ArcadeBackgroundState.initial());
|
||||
|
||||
void onCharacterSelected(CharacterTheme characterTheme) {
|
||||
emit(ArcadeBackgroundState(characterTheme: characterTheme));
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
part of 'arcade_background_cubit.dart';
|
||||
|
||||
class ArcadeBackgroundState extends Equatable {
|
||||
const ArcadeBackgroundState({required this.characterTheme});
|
||||
|
||||
const ArcadeBackgroundState.initial()
|
||||
: this(characterTheme: const DashTheme());
|
||||
|
||||
final CharacterTheme characterTheme;
|
||||
|
||||
@override
|
||||
List<Object> get props => [characterTheme];
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
// 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:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_theme/pinball_theme.dart' as theme;
|
||||
|
||||
import '../../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
final assets = [
|
||||
theme.Assets.images.android.background.keyName,
|
||||
theme.Assets.images.dash.background.keyName,
|
||||
theme.Assets.images.dino.background.keyName,
|
||||
theme.Assets.images.sparky.background.keyName,
|
||||
];
|
||||
|
||||
final flameTester = FlameTester(() => TestGame(assets));
|
||||
|
||||
group('ArcadeBackground', () {
|
||||
test(
|
||||
'can be instantiated',
|
||||
() {
|
||||
expect(ArcadeBackground(), isA<ArcadeBackground>());
|
||||
expect(ArcadeBackground.test(), isA<ArcadeBackground>());
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test(
|
||||
'loads correctly',
|
||||
(game) async {
|
||||
final ball = ArcadeBackground();
|
||||
await game.ready();
|
||||
await game.ensureAdd(ball);
|
||||
|
||||
expect(game.contains(ball), isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test(
|
||||
'has only one SpriteComponent',
|
||||
(game) async {
|
||||
final ball = ArcadeBackground();
|
||||
await game.ready();
|
||||
await game.ensureAdd(ball);
|
||||
|
||||
expect(
|
||||
ball.descendants().whereType<SpriteComponent>().length,
|
||||
equals(1),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test(
|
||||
'ArcadeBackgroundSpriteComponent changes sprite onNewState',
|
||||
(game) async {
|
||||
final ball = ArcadeBackground();
|
||||
await game.ready();
|
||||
await game.ensureAdd(ball);
|
||||
|
||||
final ballSprite = ball
|
||||
.descendants()
|
||||
.whereType<ArcadeBackgroundSpriteComponent>()
|
||||
.single;
|
||||
final originalSprite = ballSprite.sprite;
|
||||
|
||||
ballSprite.onNewState(
|
||||
const ArcadeBackgroundState(characterTheme: theme.DinoTheme()),
|
||||
);
|
||||
await game.ready();
|
||||
|
||||
final newSprite = ballSprite.sprite;
|
||||
expect(newSprite != originalSprite, isTrue);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_theme/pinball_theme.dart';
|
||||
|
||||
void main() {
|
||||
group(
|
||||
'ArcadeBackgroundCubit',
|
||||
() {
|
||||
blocTest<ArcadeBackgroundCubit, ArcadeBackgroundState>(
|
||||
'onCharacterSelected emits new theme',
|
||||
build: ArcadeBackgroundCubit.new,
|
||||
act: (bloc) => bloc.onCharacterSelected(const DinoTheme()),
|
||||
expect: () => [
|
||||
const ArcadeBackgroundState(
|
||||
characterTheme: DinoTheme(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_theme/pinball_theme.dart';
|
||||
|
||||
void main() {
|
||||
group('ArcadeBackgroundState', () {
|
||||
test('supports value equality', () {
|
||||
expect(
|
||||
ArcadeBackgroundState(characterTheme: DashTheme()),
|
||||
equals(ArcadeBackgroundState(characterTheme: DashTheme())),
|
||||
);
|
||||
});
|
||||
|
||||
group('constructor', () {
|
||||
test('can be instantiated', () {
|
||||
expect(
|
||||
ArcadeBackgroundState(characterTheme: DashTheme()),
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('initial contains DashTheme', () {
|
||||
expect(
|
||||
ArcadeBackgroundState.initial().characterTheme,
|
||||
DashTheme(),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
After Width: | Height: | Size: 638 KiB |
Before Width: | Height: | Size: 250 KiB |
After Width: | Height: | Size: 646 KiB |
Before Width: | Height: | Size: 342 KiB |
After Width: | Height: | Size: 654 KiB |
Before Width: | Height: | Size: 146 KiB |
After Width: | Height: | Size: 660 KiB |
Before Width: | Height: | Size: 372 KiB |
@ -0,0 +1,112 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:flame_bloc/flame_bloc.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:pinball/game/bloc/game_bloc.dart';
|
||||
import 'package:pinball/game/components/backbox/displays/share_display.dart';
|
||||
import 'package:pinball/l10n/l10n.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
class _TestGame extends Forge2DGame with HasTappables {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
images.prefix = '';
|
||||
await images.loadAll(
|
||||
[
|
||||
Assets.images.backbox.button.facebook.keyName,
|
||||
Assets.images.backbox.button.twitter.keyName,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> pump(ShareDisplay component) {
|
||||
return ensureAdd(
|
||||
FlameBlocProvider<GameBloc, GameState>.value(
|
||||
value: GameBloc(),
|
||||
children: [
|
||||
FlameProvider.value(
|
||||
_MockAppLocalizations(),
|
||||
children: [component],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MockAppLocalizations extends Mock implements AppLocalizations {
|
||||
@override
|
||||
String get letEveryone => '';
|
||||
|
||||
@override
|
||||
String get bySharingYourScore => '';
|
||||
|
||||
@override
|
||||
String get socialMediaAccount => '';
|
||||
}
|
||||
|
||||
class _MockTapDownInfo extends Mock implements TapDownInfo {}
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
final flameTester = FlameTester(_TestGame.new);
|
||||
|
||||
group('ShareDisplay', () {
|
||||
flameTester.test(
|
||||
'loads correctly',
|
||||
(game) async {
|
||||
final component = ShareDisplay();
|
||||
await game.pump(component);
|
||||
expect(game.descendants(), contains(component));
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test(
|
||||
'calls onShare when Facebook button is tapped',
|
||||
(game) async {
|
||||
var tapped = false;
|
||||
|
||||
final tapDownInfo = _MockTapDownInfo();
|
||||
final component = ShareDisplay(
|
||||
onShare: (_) => tapped = true,
|
||||
);
|
||||
await game.pump(component);
|
||||
|
||||
final facebookButton =
|
||||
component.descendants().whereType<FacebookButtonComponent>().first;
|
||||
|
||||
facebookButton.onTapDown(tapDownInfo);
|
||||
|
||||
expect(tapped, isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test(
|
||||
'calls onShare when Twitter button is tapped',
|
||||
(game) async {
|
||||
var tapped = false;
|
||||
|
||||
final tapDownInfo = _MockTapDownInfo();
|
||||
final component = ShareDisplay(
|
||||
onShare: (_) => tapped = true,
|
||||
);
|
||||
await game.pump(component);
|
||||
|
||||
final twitterButton =
|
||||
component.descendants().whereType<TwitterButtonComponent>().first;
|
||||
|
||||
twitterButton.onTapDown(tapDownInfo);
|
||||
|
||||
expect(tapped, isTrue);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|