Merge branch 'main' into fix/remove-mobile-backgrounds

pull/411/head
Allison Ryan 3 years ago
commit 1bd1f45d99

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 KiB

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

@ -9,20 +9,24 @@ import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_ui/pinball_ui.dart';
import 'package:share_repository/share_repository.dart';
class App extends StatelessWidget {
const App({
Key? key,
required AuthenticationRepository authenticationRepository,
required LeaderboardRepository leaderboardRepository,
required ShareRepository shareRepository,
required PinballAudioPlayer pinballAudioPlayer,
}) : _authenticationRepository = authenticationRepository,
_leaderboardRepository = leaderboardRepository,
_shareRepository = shareRepository,
_pinballAudioPlayer = pinballAudioPlayer,
super(key: key);
final AuthenticationRepository _authenticationRepository;
final LeaderboardRepository _leaderboardRepository;
final ShareRepository _shareRepository;
final PinballAudioPlayer _pinballAudioPlayer;
@override
@ -31,6 +35,7 @@ class App extends StatelessWidget {
providers: [
RepositoryProvider.value(value: _authenticationRepository),
RepositoryProvider.value(value: _leaderboardRepository),
RepositoryProvider.value(value: _shareRepository),
RepositoryProvider.value(value: _pinballAudioPlayer),
],
child: MultiBlocProvider(

@ -18,7 +18,7 @@ class AssetsManagerCubit extends Cubit<AssetsManagerState> {
/// delay here, which is a bit random in duration but enough to let the UI
/// do its job without adding too much delay for the user, we are letting
/// the UI paint first, and then we start loading the assets.
await Future<void>.delayed(const Duration(milliseconds: 300));
await Future<void>.delayed(const Duration(seconds: 1));
emit(
state.copyWith(
loadables: [

@ -4,4 +4,6 @@ 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 'kicker_noise_behavior.dart';
export 'scoring_behavior.dart';

@ -0,0 +1,13 @@
// ignore_for_file: public_member_api_docs
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_flame/pinball_flame.dart';
class CowBumperNoiseBehavior extends ContactBehavior {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
readProvider<PinballAudioPlayer>().play(PinballAudio.cowMoo);
}
}

@ -0,0 +1,11 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_flame/pinball_flame.dart';
class KickerNoiseBehavior extends ContactBehavior {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
readProvider<PinballAudioPlayer>().play(PinballAudio.kicker);
}
}

@ -16,6 +16,8 @@ class GameBloc extends Bloc<GameEvent, GameState> {
on<GameStarted>(_onGameStarted);
}
static const _maxScore = 9999999999;
void _onGameStarted(GameStarted _, Emitter emit) {
emit(state.copyWith(status: GameStatus.playing));
}
@ -25,7 +27,10 @@ class GameBloc extends Bloc<GameEvent, GameState> {
}
void _onRoundLost(RoundLost event, Emitter emit) {
final score = state.totalScore + state.roundScore * state.multiplier;
final score = math.min(
state.totalScore + state.roundScore * state.multiplier,
_maxScore,
);
final roundsLeft = math.max(state.rounds - 1, 0);
emit(
@ -41,9 +46,11 @@ class GameBloc extends Bloc<GameEvent, GameState> {
void _onScored(Scored event, Emitter emit) {
if (state.status.isPlaying) {
emit(
state.copyWith(roundScore: state.roundScore + event.points),
final combinedScore = math.min(
state.totalScore + state.roundScore + event.points,
_maxScore,
);
emit(state.copyWith(roundScore: combinedScore - state.totalScore));
}
}

@ -48,6 +48,7 @@ class AndroidAcres extends Component {
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoiseBehavior(),
CowBumperNoiseBehavior(),
],
)..initialPosition = Vector2(-20.7, -13),
AndroidSpaceshipBonusBehavior(),

@ -6,10 +6,13 @@ import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/game/components/backbox/bloc/backbox_bloc.dart';
import 'package:pinball/game/components/backbox/displays/displays.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' hide Assets;
import 'package:pinball_ui/pinball_ui.dart';
import 'package:platform_helper/platform_helper.dart';
import 'package:share_repository/share_repository.dart';
/// {@template backbox}
/// The [Backbox] of the pinball machine.
@ -18,21 +21,26 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef {
/// {@macro backbox}
Backbox({
required LeaderboardRepository leaderboardRepository,
required ShareRepository shareRepository,
required List<LeaderboardEntryData>? entries,
}) : _bloc = BackboxBloc(
leaderboardRepository: leaderboardRepository,
initialEntries: entries,
),
_shareRepository = shareRepository,
_platformHelper = PlatformHelper();
/// {@macro backbox}
@visibleForTesting
Backbox.test({
required BackboxBloc bloc,
required ShareRepository shareRepository,
required PlatformHelper platformHelper,
}) : _bloc = bloc,
_shareRepository = shareRepository,
_platformHelper = platformHelper;
final ShareRepository _shareRepository;
late final Component _display;
final BackboxBloc _bloc;
final PlatformHelper _platformHelper;
@ -87,6 +95,8 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef {
),
);
} else if (state is InitialsSuccessState) {
gameRef.overlays.remove(PinballGame.mobileControlsOverlay);
_display.add(
GameOverInfoDisplay(
onShare: () {
@ -94,6 +104,20 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef {
},
),
);
} else if (state is ShareState) {
_display.add(
ShareDisplay(
onShare: (platform) {
final message = readProvider<AppLocalizations>()
.iGotScoreAtPinball(state.score);
final url = _shareRepository.shareText(
value: message,
platform: platform,
);
openLink(url);
},
),
);
} else if (state is InitialsFailureState) {
_display.add(
InitialsSubmissionFailureDisplay(

@ -75,9 +75,7 @@ class BackboxBloc extends Bloc<BackboxEvent, BackboxState> {
Emitter<BackboxState> emit,
) async {
emit(
ShareState(
score: event.score,
),
ShareState(score: event.score),
);
}

@ -5,3 +5,4 @@ export 'initials_submission_success_display.dart';
export 'leaderboard_display.dart';
export 'leaderboard_failure_display.dart';
export 'loading_display.dart';
export 'share_display.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;
}
}

@ -52,6 +52,7 @@ class _BottomGroupSide extends Component {
children: [
ScoringContactBehavior(points: Points.fiveThousand)
..applyTo(['bouncy_edge']),
KickerNoiseBehavior()..applyTo(['bouncy_edge']),
],
)..initialPosition = Vector2(
(22.44 * direction) + centerXAdjustment,

@ -102,6 +102,8 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.sparky.bumper.c.dimmed.keyName),
images.load(components.Assets.images.backbox.marquee.keyName),
images.load(components.Assets.images.backbox.displayDivider.keyName),
images.load(components.Assets.images.backbox.button.facebook.keyName),
images.load(components.Assets.images.backbox.button.twitter.keyName),
images.load(
components.Assets.images.backbox.displayTitleDecoration.keyName,
),

@ -15,12 +15,14 @@ import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:platform_helper/platform_helper.dart';
import 'package:share_repository/share_repository.dart';
class PinballGame extends PinballForge2DGame
with HasKeyboardHandlerComponents, MultiTouchTapDetector, HasTappables {
PinballGame({
required CharacterThemeCubit characterThemeBloc,
required this.leaderboardRepository,
required this.shareRepository,
required GameBloc gameBloc,
required AppLocalizations l10n,
required PinballAudioPlayer audioPlayer,
@ -52,6 +54,8 @@ class PinballGame extends PinballForge2DGame
final LeaderboardRepository leaderboardRepository;
final ShareRepository shareRepository;
final AppLocalizations _l10n;
final GameBloc _gameBloc;
@ -85,6 +89,7 @@ class PinballGame extends PinballForge2DGame
providers: [
FlameProvider<PinballAudioPlayer>.value(_audioPlayer),
FlameProvider<LeaderboardRepository>.value(leaderboardRepository),
FlameProvider<ShareRepository>.value(shareRepository),
FlameProvider<AppLocalizations>.value(_l10n),
],
children: [
@ -107,6 +112,7 @@ class PinballGame extends PinballForge2DGame
Boundaries(),
Backbox(
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
entries: _entries,
),
GoogleWord(position: Vector2(-4.45, 1.8)),
@ -189,6 +195,7 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
DebugPinballGame({
required CharacterThemeCubit characterThemeBloc,
required LeaderboardRepository leaderboardRepository,
required ShareRepository shareRepository,
required AppLocalizations l10n,
required PinballAudioPlayer audioPlayer,
required GameBloc gameBloc,
@ -196,6 +203,7 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
characterThemeBloc: characterThemeBloc,
audioPlayer: audioPlayer,
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
l10n: l10n,
gameBloc: gameBloc,
);

@ -5,13 +5,13 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/assets_manager/assets_manager.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/gen/gen.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/more_information/more_information.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_ui/pinball_ui.dart';
import 'package:share_repository/share_repository.dart';
class PinballGamePage extends StatelessWidget {
const PinballGamePage({
@ -26,12 +26,14 @@ class PinballGamePage extends StatelessWidget {
final characterThemeBloc = context.read<CharacterThemeCubit>();
final audioPlayer = context.read<PinballAudioPlayer>();
final leaderboardRepository = context.read<LeaderboardRepository>();
final shareRepository = context.read<ShareRepository>();
final gameBloc = context.read<GameBloc>();
final game = isDebugMode
? DebugPinballGame(
characterThemeBloc: characterThemeBloc,
audioPlayer: audioPlayer,
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
l10n: context.l10n,
gameBloc: gameBloc,
)
@ -39,6 +41,7 @@ class PinballGamePage extends StatelessWidget {
characterThemeBloc: characterThemeBloc,
audioPlayer: audioPlayer,
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
l10n: context.l10n,
gameBloc: gameBloc,
);
@ -163,7 +166,7 @@ class _PositionedInfoIcon extends StatelessWidget {
visible: state.status.isGameOver,
child: IconButton(
iconSize: 50,
icon: Assets.images.linkBox.infoIcon.image(),
icon: const Icon(Icons.info, color: PinballColors.white),
onPressed: () => showMoreInformationDialog(context),
),
);

@ -14,7 +14,6 @@ class $AssetsImagesGen {
const $AssetsImagesBonusAnimationGen();
$AssetsImagesComponentsGen get components =>
const $AssetsImagesComponentsGen();
$AssetsImagesLinkBoxGen get linkBox => const $AssetsImagesLinkBoxGen();
$AssetsImagesLoadingGameGen get loadingGame =>
const $AssetsImagesLoadingGameGen();
$AssetsImagesScoreGen get score => const $AssetsImagesScoreGen();
@ -56,14 +55,6 @@ class $AssetsImagesComponentsGen {
const AssetGenImage('assets/images/components/space.png');
}
class $AssetsImagesLinkBoxGen {
const $AssetsImagesLinkBoxGen();
/// File path: assets/images/link_box/info_icon.png
AssetGenImage get infoIcon =>
const AssetGenImage('assets/images/link_box/info_icon.png');
}
class $AssetsImagesLoadingGameGen {
const $AssetsImagesLoadingGameGen();

@ -189,7 +189,7 @@
"description": "Text shown on the mobile controls enter button"
},
"initialsErrorTitle": "Uh-oh... well, that didnt work",
"@enter": {
"@initialsErrorTitle": {
"description": "Title shown when the initials submission fails"
},
"initialsErrorMessage": "Please try a different combination of letters",
@ -200,4 +200,26 @@
"@leaderboardErrorMessage": {
"description": "Text shown when the leaderboard had an error while loading"
}
,
"letEveryone": "Let everyone know about I/O Pinball",
"@letEveryone": {
"description": "Text displayed on share screen for description"
},
"bySharingYourScore": "by sharing your score to your preferred",
"@bySharingYourScore": {
"description": "Text displayed on share screen for description"
},
"socialMediaAccount": "social media account!",
"@socialMediaAccount": {
"description": "Text displayed on share screen for description"
},
"iGotScoreAtPinball": "I got {score} at the #IOPinball machine, can you beat my score? See you at #GoogleIO!",
"@iGotScoreAtPinball": {
"description": "Text to share score on Social Network",
"placeholders": {
"score": {
"type": "int"
}
}
}
}

@ -6,10 +6,13 @@ import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/app/app.dart';
import 'package:pinball/bootstrap.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:share_repository/share_repository.dart';
void main() {
bootstrap((firestore, firebaseAuth) async {
final leaderboardRepository = LeaderboardRepository(firestore);
const shareRepository =
ShareRepository(appUrl: ShareRepository.pinballGameUrl);
final authenticationRepository = AuthenticationRepository(firebaseAuth);
final pinballAudioPlayer = PinballAudioPlayer();
unawaited(
@ -20,6 +23,7 @@ void main() {
return App(
authenticationRepository: authenticationRepository,
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
pinballAudioPlayer: pinballAudioPlayer,
);
});

@ -17,11 +17,14 @@ class $AssetsSfxGen {
String get android => 'assets/sfx/android.mp3';
String get bumperA => 'assets/sfx/bumper_a.mp3';
String get bumperB => 'assets/sfx/bumper_b.mp3';
String get cowMoo => 'assets/sfx/cow_moo.mp3';
String get dash => 'assets/sfx/dash.mp3';
String get dino => 'assets/sfx/dino.mp3';
String get gameOverVoiceOver => 'assets/sfx/game_over_voice_over.mp3';
String get google => 'assets/sfx/google.mp3';
String get ioPinballVoiceOver => 'assets/sfx/io_pinball_voice_over.mp3';
String get kickerA => 'assets/sfx/kicker_a.mp3';
String get kickerB => 'assets/sfx/kicker_b.mp3';
String get launcher => 'assets/sfx/launcher.mp3';
String get sparky => 'assets/sfx/sparky.mp3';
}

@ -1,32 +1,39 @@
import 'dart:math';
import 'package:audioplayers/audioplayers.dart';
import 'package:clock/clock.dart';
import 'package:flame_audio/audio_pool.dart';
import 'package:flame_audio/flame_audio.dart';
import 'package:flutter/material.dart';
import 'package:pinball_audio/gen/assets.gen.dart';
/// Sounds available for play
/// Sounds available to play.
enum PinballAudio {
/// Google
/// Google.
google,
/// Bumper
/// Bumper.
bumper,
/// Background music
/// Cow moo.
cowMoo,
/// Background music.
backgroundMusic,
/// IO Pinball voice over
/// IO Pinball voice over.
ioPinballVoiceOver,
/// Game over
/// Game over.
gameOverVoiceOver,
/// Launcher
/// Launcher.
launcher,
/// Sparky
/// Kicker.
kicker,
/// Sparky.
sparky,
/// Android
@ -109,44 +116,79 @@ class _LoopAudio extends _Audio {
}
}
class _BumperAudio extends _Audio {
_BumperAudio({
class _RandomABAudio extends _Audio {
_RandomABAudio({
required this.createAudioPool,
required this.seed,
required this.audioAssetA,
required this.audioAssetB,
this.volume,
});
final CreateAudioPool createAudioPool;
final Random seed;
final String audioAssetA;
final String audioAssetB;
final double? volume;
late AudioPool bumperA;
late AudioPool bumperB;
late AudioPool audioA;
late AudioPool audioB;
@override
Future<void> load() async {
await Future.wait(
[
createAudioPool(
prefixFile(Assets.sfx.bumperA),
prefixFile(audioAssetA),
maxPlayers: 4,
prefix: '',
).then((pool) => bumperA = pool),
).then((pool) => audioA = pool),
createAudioPool(
prefixFile(Assets.sfx.bumperB),
prefixFile(audioAssetB),
maxPlayers: 4,
prefix: '',
).then((pool) => bumperB = pool),
).then((pool) => audioB = pool),
],
);
}
@override
void play() {
(seed.nextBool() ? bumperA : bumperB).start(volume: 0.6);
(seed.nextBool() ? audioA : audioB).start(volume: volume ?? 1);
}
}
class _ThrottledAudio extends _Audio {
_ThrottledAudio({
required this.preCacheSingleAudio,
required this.playSingleAudio,
required this.path,
required this.duration,
});
final PreCacheSingleAudio preCacheSingleAudio;
final PlaySingleAudio playSingleAudio;
final String path;
final Duration duration;
DateTime? _lastPlayed;
@override
Future<void> load() => preCacheSingleAudio(prefixFile(path));
@override
void play() {
final now = clock.now();
if (_lastPlayed == null ||
(_lastPlayed != null && now.difference(_lastPlayed!) > duration)) {
_lastPlayed = now;
playSingleAudio(prefixFile(path));
}
}
}
/// {@template pinball_audio_player}
/// Sound manager for the pinball game
/// Sound manager for the pinball game.
/// {@endtemplate}
class PinballAudioPlayer {
/// {@macro pinball_audio_player}
@ -208,9 +250,25 @@ class PinballAudioPlayer {
playSingleAudio: _playSingleAudio,
path: Assets.sfx.gameOverVoiceOver,
),
PinballAudio.bumper: _BumperAudio(
PinballAudio.bumper: _RandomABAudio(
createAudioPool: _createAudioPool,
seed: _seed,
audioAssetA: Assets.sfx.bumperA,
audioAssetB: Assets.sfx.bumperB,
volume: 0.6,
),
PinballAudio.kicker: _RandomABAudio(
createAudioPool: _createAudioPool,
seed: _seed,
audioAssetA: Assets.sfx.kickerA,
audioAssetB: Assets.sfx.kickerB,
volume: 0.6,
),
PinballAudio.cowMoo: _ThrottledAudio(
preCacheSingleAudio: _preCacheSingleAudio,
playSingleAudio: _playSingleAudio,
path: Assets.sfx.cowMoo,
duration: const Duration(seconds: 2),
),
PinballAudio.backgroundMusic: _LoopAudio(
preCacheSingleAudio: _preCacheSingleAudio,
@ -232,19 +290,19 @@ class PinballAudioPlayer {
final Random _seed;
/// Registered audios on the Player
/// Registered audios on the Player.
@visibleForTesting
// ignore: library_private_types_in_public_api
late final Map<PinballAudio, _Audio> audios;
/// Loads the sounds effects into the memory
/// Loads the sounds effects into the memory.
List<Future<void>> load() {
_configureAudioCache(FlameAudio.audioCache);
return audios.values.map((a) => a.load()).toList();
}
/// Plays the received audio
/// Plays the received audio.
void play(PinballAudio audio) {
assert(
audios.containsKey(audio),

@ -8,6 +8,7 @@ environment:
dependencies:
audioplayers: ^0.20.1
clock: ^1.1.0
flame_audio: ^1.0.1
flutter:
sdk: flutter

@ -2,6 +2,7 @@
import 'dart:math';
import 'package:audioplayers/audioplayers.dart';
import 'package:clock/clock.dart';
import 'package:flame_audio/audio_pool.dart';
import 'package:flame_audio/flame_audio.dart';
import 'package:flutter_test/flutter_test.dart';
@ -43,6 +44,8 @@ class _MockPreCacheSingleAudio extends Mock implements _PreCacheSingleAudio {}
class _MockRandom extends Mock implements Random {}
class _MockClock extends Mock implements Clock {}
void main() {
group('PinballAudio', () {
late _MockCreateAudioPool createAudioPool;
@ -116,6 +119,26 @@ void main() {
).called(1);
});
test('creates the kicker pools', () async {
await Future.wait(audioPlayer.load());
verify(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.kickerA}',
maxPlayers: 4,
prefix: '',
),
).called(1);
verify(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.kickerB}',
maxPlayers: 4,
prefix: '',
),
).called(1);
});
test('configures the audio cache instance', () async {
await Future.wait(audioPlayer.load());
@ -171,6 +194,10 @@ void main() {
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/launcher.mp3'),
).called(1);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/cow_moo.mp3'),
).called(1);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/music/background.mp3'),
@ -227,6 +254,91 @@ void main() {
});
});
group('kicker', () {
late AudioPool kickerAPool;
late AudioPool kickerBPool;
setUp(() {
kickerAPool = _MockAudioPool();
when(() => kickerAPool.start(volume: any(named: 'volume')))
.thenAnswer((_) async => () {});
when(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.kickerA}',
maxPlayers: any(named: 'maxPlayers'),
prefix: any(named: 'prefix'),
),
).thenAnswer((_) async => kickerAPool);
kickerBPool = _MockAudioPool();
when(() => kickerBPool.start(volume: any(named: 'volume')))
.thenAnswer((_) async => () {});
when(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.kickerB}',
maxPlayers: any(named: 'maxPlayers'),
prefix: any(named: 'prefix'),
),
).thenAnswer((_) async => kickerBPool);
});
group('when seed is true', () {
test('plays the kicker A sound pool', () async {
when(seed.nextBool).thenReturn(true);
await Future.wait(audioPlayer.load());
audioPlayer.play(PinballAudio.kicker);
verify(() => kickerAPool.start(volume: 0.6)).called(1);
});
});
group('when seed is false', () {
test('plays the kicker B sound pool', () async {
when(seed.nextBool).thenReturn(false);
await Future.wait(audioPlayer.load());
audioPlayer.play(PinballAudio.kicker);
verify(() => kickerBPool.start(volume: 0.6)).called(1);
});
});
});
group('cow moo', () {
test('plays the correct file', () async {
await Future.wait(audioPlayer.load());
audioPlayer.play(PinballAudio.cowMoo);
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'),
).called(1);
});
test('only plays the sound again after 2 seconds', () async {
final clock = _MockClock();
await withClock(clock, () async {
when(clock.now).thenReturn(DateTime(2022));
await Future.wait(audioPlayer.load());
audioPlayer
..play(PinballAudio.cowMoo)
..play(PinballAudio.cowMoo);
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'),
).called(1);
when(clock.now).thenReturn(DateTime(2022, 1, 1, 1, 2));
audioPlayer.play(PinballAudio.cowMoo);
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'),
).called(1);
});
});
});
group('google', () {
test('plays the correct file', () async {
await Future.wait(audioPlayer.load());

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 KiB

After

Width:  |  Height:  |  Size: 544 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 KiB

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1012 KiB

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 481 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

@ -59,6 +59,9 @@ class $AssetsImagesAndroidGen {
class $AssetsImagesBackboxGen {
const $AssetsImagesBackboxGen();
$AssetsImagesBackboxButtonGen get button =>
const $AssetsImagesBackboxButtonGen();
/// File path: assets/images/backbox/display_divider.png
AssetGenImage get displayDivider =>
const AssetGenImage('assets/images/backbox/display_divider.png');
@ -386,6 +389,18 @@ class $AssetsImagesAndroidSpaceshipGen {
const AssetGenImage('assets/images/android/spaceship/saucer.png');
}
class $AssetsImagesBackboxButtonGen {
const $AssetsImagesBackboxButtonGen();
/// File path: assets/images/backbox/button/facebook.png
AssetGenImage get facebook =>
const AssetGenImage('assets/images/backbox/button/facebook.png');
/// File path: assets/images/backbox/button/twitter.png
AssetGenImage get twitter =>
const AssetGenImage('assets/images/backbox/button/twitter.png');
}
class $AssetsImagesDashBumperGen {
const $AssetsImagesDashBumperGen();

@ -90,6 +90,7 @@ flutter:
- assets/images/multiplier/x6/
- assets/images/score/
- assets/images/backbox/
- assets/images/backbox/button/
- assets/images/flapper/
- assets/images/skill_shot/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 915 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 850 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 638 KiB

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save