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/start_game/start_game.dart';
import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
import 'package:share_repository/share_repository.dart';
class App extends StatelessWidget { class App extends StatelessWidget {
const App({ const App({
Key? key, Key? key,
required AuthenticationRepository authenticationRepository, required AuthenticationRepository authenticationRepository,
required LeaderboardRepository leaderboardRepository, required LeaderboardRepository leaderboardRepository,
required ShareRepository shareRepository,
required PinballAudioPlayer pinballAudioPlayer, required PinballAudioPlayer pinballAudioPlayer,
}) : _authenticationRepository = authenticationRepository, }) : _authenticationRepository = authenticationRepository,
_leaderboardRepository = leaderboardRepository, _leaderboardRepository = leaderboardRepository,
_shareRepository = shareRepository,
_pinballAudioPlayer = pinballAudioPlayer, _pinballAudioPlayer = pinballAudioPlayer,
super(key: key); super(key: key);
final AuthenticationRepository _authenticationRepository; final AuthenticationRepository _authenticationRepository;
final LeaderboardRepository _leaderboardRepository; final LeaderboardRepository _leaderboardRepository;
final ShareRepository _shareRepository;
final PinballAudioPlayer _pinballAudioPlayer; final PinballAudioPlayer _pinballAudioPlayer;
@override @override
@ -31,6 +35,7 @@ class App extends StatelessWidget {
providers: [ providers: [
RepositoryProvider.value(value: _authenticationRepository), RepositoryProvider.value(value: _authenticationRepository),
RepositoryProvider.value(value: _leaderboardRepository), RepositoryProvider.value(value: _leaderboardRepository),
RepositoryProvider.value(value: _shareRepository),
RepositoryProvider.value(value: _pinballAudioPlayer), RepositoryProvider.value(value: _pinballAudioPlayer),
], ],
child: MultiBlocProvider( 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 /// 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 /// 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. /// 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( emit(
state.copyWith( state.copyWith(
loadables: [ loadables: [

@ -4,4 +4,6 @@ export 'bonus_noise_behavior.dart';
export 'bumper_noise_behavior.dart'; export 'bumper_noise_behavior.dart';
export 'camera_focusing_behavior.dart'; export 'camera_focusing_behavior.dart';
export 'character_selection_behavior.dart'; export 'character_selection_behavior.dart';
export 'cow_bumper_noise_behavior.dart';
export 'kicker_noise_behavior.dart';
export 'scoring_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); on<GameStarted>(_onGameStarted);
} }
static const _maxScore = 9999999999;
void _onGameStarted(GameStarted _, Emitter emit) { void _onGameStarted(GameStarted _, Emitter emit) {
emit(state.copyWith(status: GameStatus.playing)); emit(state.copyWith(status: GameStatus.playing));
} }
@ -25,7 +27,10 @@ class GameBloc extends Bloc<GameEvent, GameState> {
} }
void _onRoundLost(RoundLost event, Emitter emit) { 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); final roundsLeft = math.max(state.rounds - 1, 0);
emit( emit(
@ -41,9 +46,11 @@ class GameBloc extends Bloc<GameEvent, GameState> {
void _onScored(Scored event, Emitter emit) { void _onScored(Scored event, Emitter emit) {
if (state.status.isPlaying) { if (state.status.isPlaying) {
emit( final combinedScore = math.min(
state.copyWith(roundScore: state.roundScore + event.points), state.totalScore + state.roundScore + event.points,
_maxScore,
); );
emit(state.copyWith(roundScore: combinedScore - state.totalScore));
} }
} }

@ -48,6 +48,7 @@ class AndroidAcres extends Component {
children: [ children: [
ScoringContactBehavior(points: Points.twentyThousand), ScoringContactBehavior(points: Points.twentyThousand),
BumperNoiseBehavior(), BumperNoiseBehavior(),
CowBumperNoiseBehavior(),
], ],
)..initialPosition = Vector2(-20.7, -13), )..initialPosition = Vector2(-20.7, -13),
AndroidSpaceshipBonusBehavior(), 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/bloc/backbox_bloc.dart';
import 'package:pinball/game/components/backbox/displays/displays.dart'; import 'package:pinball/game/components/backbox/displays/displays.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' hide Assets; 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:platform_helper/platform_helper.dart';
import 'package:share_repository/share_repository.dart';
/// {@template backbox} /// {@template backbox}
/// The [Backbox] of the pinball machine. /// The [Backbox] of the pinball machine.
@ -18,21 +21,26 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef {
/// {@macro backbox} /// {@macro backbox}
Backbox({ Backbox({
required LeaderboardRepository leaderboardRepository, required LeaderboardRepository leaderboardRepository,
required ShareRepository shareRepository,
required List<LeaderboardEntryData>? entries, required List<LeaderboardEntryData>? entries,
}) : _bloc = BackboxBloc( }) : _bloc = BackboxBloc(
leaderboardRepository: leaderboardRepository, leaderboardRepository: leaderboardRepository,
initialEntries: entries, initialEntries: entries,
), ),
_shareRepository = shareRepository,
_platformHelper = PlatformHelper(); _platformHelper = PlatformHelper();
/// {@macro backbox} /// {@macro backbox}
@visibleForTesting @visibleForTesting
Backbox.test({ Backbox.test({
required BackboxBloc bloc, required BackboxBloc bloc,
required ShareRepository shareRepository,
required PlatformHelper platformHelper, required PlatformHelper platformHelper,
}) : _bloc = bloc, }) : _bloc = bloc,
_shareRepository = shareRepository,
_platformHelper = platformHelper; _platformHelper = platformHelper;
final ShareRepository _shareRepository;
late final Component _display; late final Component _display;
final BackboxBloc _bloc; final BackboxBloc _bloc;
final PlatformHelper _platformHelper; final PlatformHelper _platformHelper;
@ -87,6 +95,8 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef {
), ),
); );
} else if (state is InitialsSuccessState) { } else if (state is InitialsSuccessState) {
gameRef.overlays.remove(PinballGame.mobileControlsOverlay);
_display.add( _display.add(
GameOverInfoDisplay( GameOverInfoDisplay(
onShare: () { 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) { } else if (state is InitialsFailureState) {
_display.add( _display.add(
InitialsSubmissionFailureDisplay( InitialsSubmissionFailureDisplay(

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

@ -5,3 +5,4 @@ export 'initials_submission_success_display.dart';
export 'leaderboard_display.dart'; export 'leaderboard_display.dart';
export 'leaderboard_failure_display.dart'; export 'leaderboard_failure_display.dart';
export 'loading_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: [ children: [
ScoringContactBehavior(points: Points.fiveThousand) ScoringContactBehavior(points: Points.fiveThousand)
..applyTo(['bouncy_edge']), ..applyTo(['bouncy_edge']),
KickerNoiseBehavior()..applyTo(['bouncy_edge']),
], ],
)..initialPosition = Vector2( )..initialPosition = Vector2(
(22.44 * direction) + centerXAdjustment, (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.sparky.bumper.c.dimmed.keyName),
images.load(components.Assets.images.backbox.marquee.keyName), images.load(components.Assets.images.backbox.marquee.keyName),
images.load(components.Assets.images.backbox.displayDivider.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( images.load(
components.Assets.images.backbox.displayTitleDecoration.keyName, 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_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
import 'package:platform_helper/platform_helper.dart'; import 'package:platform_helper/platform_helper.dart';
import 'package:share_repository/share_repository.dart';
class PinballGame extends PinballForge2DGame class PinballGame extends PinballForge2DGame
with HasKeyboardHandlerComponents, MultiTouchTapDetector, HasTappables { with HasKeyboardHandlerComponents, MultiTouchTapDetector, HasTappables {
PinballGame({ PinballGame({
required CharacterThemeCubit characterThemeBloc, required CharacterThemeCubit characterThemeBloc,
required this.leaderboardRepository, required this.leaderboardRepository,
required this.shareRepository,
required GameBloc gameBloc, required GameBloc gameBloc,
required AppLocalizations l10n, required AppLocalizations l10n,
required PinballAudioPlayer audioPlayer, required PinballAudioPlayer audioPlayer,
@ -52,6 +54,8 @@ class PinballGame extends PinballForge2DGame
final LeaderboardRepository leaderboardRepository; final LeaderboardRepository leaderboardRepository;
final ShareRepository shareRepository;
final AppLocalizations _l10n; final AppLocalizations _l10n;
final GameBloc _gameBloc; final GameBloc _gameBloc;
@ -85,6 +89,7 @@ class PinballGame extends PinballForge2DGame
providers: [ providers: [
FlameProvider<PinballAudioPlayer>.value(_audioPlayer), FlameProvider<PinballAudioPlayer>.value(_audioPlayer),
FlameProvider<LeaderboardRepository>.value(leaderboardRepository), FlameProvider<LeaderboardRepository>.value(leaderboardRepository),
FlameProvider<ShareRepository>.value(shareRepository),
FlameProvider<AppLocalizations>.value(_l10n), FlameProvider<AppLocalizations>.value(_l10n),
], ],
children: [ children: [
@ -107,6 +112,7 @@ class PinballGame extends PinballForge2DGame
Boundaries(), Boundaries(),
Backbox( Backbox(
leaderboardRepository: leaderboardRepository, leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
entries: _entries, entries: _entries,
), ),
GoogleWord(position: Vector2(-4.45, 1.8)), GoogleWord(position: Vector2(-4.45, 1.8)),
@ -189,6 +195,7 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
DebugPinballGame({ DebugPinballGame({
required CharacterThemeCubit characterThemeBloc, required CharacterThemeCubit characterThemeBloc,
required LeaderboardRepository leaderboardRepository, required LeaderboardRepository leaderboardRepository,
required ShareRepository shareRepository,
required AppLocalizations l10n, required AppLocalizations l10n,
required PinballAudioPlayer audioPlayer, required PinballAudioPlayer audioPlayer,
required GameBloc gameBloc, required GameBloc gameBloc,
@ -196,6 +203,7 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
characterThemeBloc: characterThemeBloc, characterThemeBloc: characterThemeBloc,
audioPlayer: audioPlayer, audioPlayer: audioPlayer,
leaderboardRepository: leaderboardRepository, leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
l10n: l10n, l10n: l10n,
gameBloc: gameBloc, gameBloc: gameBloc,
); );

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

@ -14,7 +14,6 @@ class $AssetsImagesGen {
const $AssetsImagesBonusAnimationGen(); const $AssetsImagesBonusAnimationGen();
$AssetsImagesComponentsGen get components => $AssetsImagesComponentsGen get components =>
const $AssetsImagesComponentsGen(); const $AssetsImagesComponentsGen();
$AssetsImagesLinkBoxGen get linkBox => const $AssetsImagesLinkBoxGen();
$AssetsImagesLoadingGameGen get loadingGame => $AssetsImagesLoadingGameGen get loadingGame =>
const $AssetsImagesLoadingGameGen(); const $AssetsImagesLoadingGameGen();
$AssetsImagesScoreGen get score => const $AssetsImagesScoreGen(); $AssetsImagesScoreGen get score => const $AssetsImagesScoreGen();
@ -56,14 +55,6 @@ class $AssetsImagesComponentsGen {
const AssetGenImage('assets/images/components/space.png'); 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 { class $AssetsImagesLoadingGameGen {
const $AssetsImagesLoadingGameGen(); const $AssetsImagesLoadingGameGen();

@ -189,7 +189,7 @@
"description": "Text shown on the mobile controls enter button" "description": "Text shown on the mobile controls enter button"
}, },
"initialsErrorTitle": "Uh-oh... well, that didnt work", "initialsErrorTitle": "Uh-oh... well, that didnt work",
"@enter": { "@initialsErrorTitle": {
"description": "Title shown when the initials submission fails" "description": "Title shown when the initials submission fails"
}, },
"initialsErrorMessage": "Please try a different combination of letters", "initialsErrorMessage": "Please try a different combination of letters",
@ -200,4 +200,26 @@
"@leaderboardErrorMessage": { "@leaderboardErrorMessage": {
"description": "Text shown when the leaderboard had an error while loading" "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/app/app.dart';
import 'package:pinball/bootstrap.dart'; import 'package:pinball/bootstrap.dart';
import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_audio/pinball_audio.dart';
import 'package:share_repository/share_repository.dart';
void main() { void main() {
bootstrap((firestore, firebaseAuth) async { bootstrap((firestore, firebaseAuth) async {
final leaderboardRepository = LeaderboardRepository(firestore); final leaderboardRepository = LeaderboardRepository(firestore);
const shareRepository =
ShareRepository(appUrl: ShareRepository.pinballGameUrl);
final authenticationRepository = AuthenticationRepository(firebaseAuth); final authenticationRepository = AuthenticationRepository(firebaseAuth);
final pinballAudioPlayer = PinballAudioPlayer(); final pinballAudioPlayer = PinballAudioPlayer();
unawaited( unawaited(
@ -20,6 +23,7 @@ void main() {
return App( return App(
authenticationRepository: authenticationRepository, authenticationRepository: authenticationRepository,
leaderboardRepository: leaderboardRepository, leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
pinballAudioPlayer: pinballAudioPlayer, pinballAudioPlayer: pinballAudioPlayer,
); );
}); });

@ -17,11 +17,14 @@ class $AssetsSfxGen {
String get android => 'assets/sfx/android.mp3'; String get android => 'assets/sfx/android.mp3';
String get bumperA => 'assets/sfx/bumper_a.mp3'; String get bumperA => 'assets/sfx/bumper_a.mp3';
String get bumperB => 'assets/sfx/bumper_b.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 dash => 'assets/sfx/dash.mp3';
String get dino => 'assets/sfx/dino.mp3'; String get dino => 'assets/sfx/dino.mp3';
String get gameOverVoiceOver => 'assets/sfx/game_over_voice_over.mp3'; String get gameOverVoiceOver => 'assets/sfx/game_over_voice_over.mp3';
String get google => 'assets/sfx/google.mp3'; String get google => 'assets/sfx/google.mp3';
String get ioPinballVoiceOver => 'assets/sfx/io_pinball_voice_over.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 launcher => 'assets/sfx/launcher.mp3';
String get sparky => 'assets/sfx/sparky.mp3'; String get sparky => 'assets/sfx/sparky.mp3';
} }

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

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

@ -2,6 +2,7 @@
import 'dart:math'; import 'dart:math';
import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers/audioplayers.dart';
import 'package:clock/clock.dart';
import 'package:flame_audio/audio_pool.dart'; import 'package:flame_audio/audio_pool.dart';
import 'package:flame_audio/flame_audio.dart'; import 'package:flame_audio/flame_audio.dart';
import 'package:flutter_test/flutter_test.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 _MockRandom extends Mock implements Random {}
class _MockClock extends Mock implements Clock {}
void main() { void main() {
group('PinballAudio', () { group('PinballAudio', () {
late _MockCreateAudioPool createAudioPool; late _MockCreateAudioPool createAudioPool;
@ -116,6 +119,26 @@ void main() {
).called(1); ).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 { test('configures the audio cache instance', () async {
await Future.wait(audioPlayer.load()); await Future.wait(audioPlayer.load());
@ -171,6 +194,10 @@ void main() {
() => preCacheSingleAudio () => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/launcher.mp3'), .onCall('packages/pinball_audio/assets/sfx/launcher.mp3'),
).called(1); ).called(1);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/cow_moo.mp3'),
).called(1);
verify( verify(
() => preCacheSingleAudio () => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/music/background.mp3'), .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', () { group('google', () {
test('plays the correct file', () async { test('plays the correct file', () async {
await Future.wait(audioPlayer.load()); 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 { class $AssetsImagesBackboxGen {
const $AssetsImagesBackboxGen(); const $AssetsImagesBackboxGen();
$AssetsImagesBackboxButtonGen get button =>
const $AssetsImagesBackboxButtonGen();
/// File path: assets/images/backbox/display_divider.png /// File path: assets/images/backbox/display_divider.png
AssetGenImage get displayDivider => AssetGenImage get displayDivider =>
const AssetGenImage('assets/images/backbox/display_divider.png'); const AssetGenImage('assets/images/backbox/display_divider.png');
@ -386,6 +389,18 @@ class $AssetsImagesAndroidSpaceshipGen {
const AssetGenImage('assets/images/android/spaceship/saucer.png'); 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 { class $AssetsImagesDashBumperGen {
const $AssetsImagesDashBumperGen(); const $AssetsImagesDashBumperGen();

@ -90,6 +90,7 @@ flutter:
- assets/images/multiplier/x6/ - assets/images/multiplier/x6/
- assets/images/score/ - assets/images/score/
- assets/images/backbox/ - assets/images/backbox/
- assets/images/backbox/button/
- assets/images/flapper/ - assets/images/flapper/
- assets/images/skill_shot/ - 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