feat: social networks `ShareDisplay` (#406)

* feat: share screen and tests

* chore: assets gen

* feat: added share repository

* feature: injecting sharerepository

* feat: share url

* feat: state for showing ShareDisplay at Backbox

* test: tests for sharing links

* test: backbox tests

* Update lib/game/components/backbox/displays/share_display.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update lib/game/components/backbox/displays/share_display.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update lib/game/components/backbox/displays/share_display.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/game/components/backbox/backbox_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/game/components/backbox/displays/share_display_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/game/components/backbox/displays/share_display_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/game/components/backbox/backbox_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/game/components/backbox/backbox_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* refactor: moved down social networks icons

* refactor: url and text for share

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
pull/414/head
Rui Miguel Alonso 3 years ago committed by GitHub
parent c3094850fd
commit 80da24b43c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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(

@ -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;
}
}

@ -101,6 +101,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,
),

@ -14,12 +14,14 @@ import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.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,
@ -51,6 +53,8 @@ class PinballGame extends PinballForge2DGame
final LeaderboardRepository leaderboardRepository;
final ShareRepository shareRepository;
final AppLocalizations _l10n;
final GameBloc _gameBloc;
@ -84,6 +88,7 @@ class PinballGame extends PinballForge2DGame
providers: [
FlameProvider<PinballAudioPlayer>.value(_audioPlayer),
FlameProvider<LeaderboardRepository>.value(leaderboardRepository),
FlameProvider<ShareRepository>.value(shareRepository),
FlameProvider<AppLocalizations>.value(_l10n),
],
children: [
@ -106,6 +111,7 @@ class PinballGame extends PinballForge2DGame
Boundaries(),
Backbox(
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
entries: _entries,
),
GoogleWord(position: Vector2(-4.45, 1.8)),
@ -188,6 +194,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,
@ -195,6 +202,7 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
characterThemeBloc: characterThemeBloc,
audioPlayer: audioPlayer,
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
l10n: l10n,
gameBloc: gameBloc,
);

@ -12,6 +12,7 @@ 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 +27,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 +42,7 @@ class PinballGamePage extends StatelessWidget {
characterThemeBloc: characterThemeBloc,
audioPlayer: audioPlayer,
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
l10n: context.l10n,
gameBloc: gameBloc,
);

@ -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,
);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 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/

@ -17,6 +17,9 @@ class ShareRepository {
/// Url to the Google IO Event.
static const googleIOEvent = 'https://events.google.com/io/';
/// Url to the Pinball game.
static const pinballGameUrl = 'https://ashehwkdkdjruejdnensjsjdne.web.app/#/';
/// Returns a url to share the [value] on the given [platform].
///
/// The returned url can be opened using the [url_launcher](https://pub.dev/packages/url_launcher) package.

@ -5,6 +5,7 @@ import 'package:mocktail/mocktail.dart';
import 'package:pinball/app/app.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:share_repository/share_repository.dart';
class _MockAuthenticationRepository extends Mock
implements AuthenticationRepository {}
@ -14,15 +15,19 @@ class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
class _MockShareRepository extends Mock implements ShareRepository {}
void main() {
group('App', () {
late AuthenticationRepository authenticationRepository;
late LeaderboardRepository leaderboardRepository;
late ShareRepository shareRepository;
late PinballAudioPlayer pinballAudioPlayer;
setUp(() {
authenticationRepository = _MockAuthenticationRepository();
leaderboardRepository = _MockLeaderboardRepository();
shareRepository = _MockShareRepository();
pinballAudioPlayer = _MockPinballAudioPlayer();
when(pinballAudioPlayer.load).thenAnswer((_) => [Future.value()]);
});
@ -32,6 +37,7 @@ void main() {
App(
authenticationRepository: authenticationRepository,
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
pinballAudioPlayer: pinballAudioPlayer,
),
);

@ -20,7 +20,10 @@ 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' as theme;
import 'package:pinball_ui/pinball_ui.dart';
import 'package:platform_helper/platform_helper.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'package:share_repository/share_repository.dart';
class _TestGame extends Forge2DGame
with HasKeyboardHandlerComponents, HasTappables {
@ -36,6 +39,8 @@ class _TestGame extends Forge2DGame
character.leaderboardIcon.keyName,
Assets.images.backbox.marquee.keyName,
Assets.images.backbox.displayDivider.keyName,
Assets.images.backbox.button.facebook.keyName,
Assets.images.backbox.button.twitter.keyName,
Assets.images.backbox.displayTitleDecoration.keyName,
]);
}
@ -75,8 +80,14 @@ class _MockBackboxBloc extends Mock implements BackboxBloc {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
class _MockShareRepository extends Mock implements ShareRepository {}
class _MockTapDownInfo extends Mock implements TapDownInfo {}
class _MockUrlLauncher extends Mock
with MockPlatformInterfaceMixin
implements UrlLauncherPlatform {}
class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get score => '';
@ -105,6 +116,15 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get loading => '';
@override
String get letEveryone => '';
@override
String get bySharingYourScore => '';
@override
String get socialMediaAccount => '';
@override
String get shareYourScore => '';
@ -134,6 +154,9 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get leaderboardErrorMessage => '';
@override
String iGotScoreAtPinball(int _) => '';
}
void main() {
@ -143,6 +166,7 @@ void main() {
late BackboxBloc bloc;
late PlatformHelper platformHelper;
late UrlLauncherPlatform urlLauncher;
setUp(() {
bloc = _MockBackboxBloc();
@ -161,6 +185,7 @@ void main() {
(game) async {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);
@ -178,6 +203,7 @@ void main() {
await game.pump(
Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
),
);
@ -199,6 +225,7 @@ void main() {
leaderboardRepository: _MockLeaderboardRepository(),
initialEntries: [LeaderboardEntryData.empty],
),
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);
@ -230,6 +257,7 @@ void main() {
);
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);
@ -258,6 +286,7 @@ void main() {
);
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);
@ -270,7 +299,8 @@ void main() {
);
flameTester.test(
'adds the mobile controls overlay when platform is mobile',
'adds the mobile controls overlay '
'when platform is mobile at InitialsFormState',
(game) async {
final bloc = _MockBackboxBloc();
final platformHelper = _MockPlatformHelper();
@ -286,6 +316,7 @@ void main() {
when(() => platformHelper.isMobile).thenReturn(true);
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);
@ -297,6 +328,33 @@ void main() {
},
);
flameTester.test(
'remove the mobile controls overlay '
'when InitialsSuccessState',
(game) async {
final bloc = _MockBackboxBloc();
final platformHelper = _MockPlatformHelper();
final state = InitialsSuccessState(score: 10);
whenListen(
bloc,
Stream<BackboxState>.empty(),
initialState: state,
);
when(() => platformHelper.isMobile).thenReturn(true);
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
game.overlays.value,
isNot(contains(PinballGame.mobileControlsOverlay)),
);
},
);
flameTester.test(
'adds InitialsSubmissionSuccessDisplay on InitialsSuccessState',
(game) async {
@ -308,6 +366,7 @@ void main() {
);
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);
@ -330,6 +389,7 @@ void main() {
);
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);
@ -359,6 +419,7 @@ void main() {
);
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);
@ -373,6 +434,145 @@ void main() {
},
);
group('ShareDisplay', () {
setUp(() async {
urlLauncher = _MockUrlLauncher();
UrlLauncherPlatform.instance = urlLauncher;
});
flameTester.test(
'adds ShareDisplay on ShareState',
(game) async {
final state = ShareState(score: 100);
whenListen(
bloc,
const Stream<InitialsSuccessState>.empty(),
initialState: state,
);
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
game.descendants().whereType<ShareDisplay>().length,
equals(1),
);
},
);
flameTester.test(
'opens Facebook link when sharing with Facebook',
(game) async {
when(() => urlLauncher.canLaunch(any()))
.thenAnswer((_) async => true);
when(
() => urlLauncher.launch(
any(),
useSafariVC: any(named: 'useSafariVC'),
useWebView: any(named: 'useWebView'),
enableJavaScript: any(named: 'enableJavaScript'),
enableDomStorage: any(named: 'enableDomStorage'),
universalLinksOnly: any(named: 'universalLinksOnly'),
headers: any(named: 'headers'),
),
).thenAnswer((_) async => true);
final state = ShareState(score: 100);
whenListen(
bloc,
const Stream<ShareState>.empty(),
initialState: state,
);
final shareRepository = _MockShareRepository();
const fakeUrl = 'http://fakeUrl';
when(
() => shareRepository.shareText(
value: any(named: 'value'),
platform: SharePlatform.facebook,
),
).thenReturn(fakeUrl);
final backbox = Backbox.test(
bloc: bloc,
shareRepository: shareRepository,
platformHelper: platformHelper,
);
await game.pump(backbox);
final facebookButton =
game.descendants().whereType<FacebookButtonComponent>().first;
facebookButton.onTapDown(_MockTapDownInfo());
await game.ready();
verify(
() => shareRepository.shareText(
value: any(named: 'value'),
platform: SharePlatform.facebook,
),
).called(1);
},
);
flameTester.test(
'opens Twitter link when sharing with Twitter',
(game) async {
final state = ShareState(score: 100);
whenListen(
bloc,
Stream.value(state),
initialState: state,
);
final shareRepository = _MockShareRepository();
const fakeUrl = 'http://fakeUrl';
when(
() => shareRepository.shareText(
value: any(named: 'value'),
platform: SharePlatform.twitter,
),
).thenReturn(fakeUrl);
when(() => urlLauncher.canLaunch(any()))
.thenAnswer((_) async => true);
when(
() => urlLauncher.launch(
any(),
useSafariVC: any(named: 'useSafariVC'),
useWebView: any(named: 'useWebView'),
enableJavaScript: any(named: 'enableJavaScript'),
enableDomStorage: any(named: 'enableDomStorage'),
universalLinksOnly: any(named: 'universalLinksOnly'),
headers: any(named: 'headers'),
),
).thenAnswer((_) async => true);
final backbox = Backbox.test(
bloc: bloc,
shareRepository: shareRepository,
platformHelper: platformHelper,
);
await game.pump(backbox);
final facebookButton =
game.descendants().whereType<TwitterButtonComponent>().first;
facebookButton.onTapDown(_MockTapDownInfo());
await game.ready();
verify(
() => shareRepository.shareText(
value: any(named: 'value'),
platform: SharePlatform.twitter,
),
).called(1);
},
);
});
flameTester.test(
'adds LeaderboardDisplay on LeaderboardSuccessState',
(game) async {
@ -384,6 +584,7 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);
@ -406,6 +607,7 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);
@ -429,6 +631,7 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);
@ -469,6 +672,7 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
);
await game.pump(backbox);

@ -137,6 +137,25 @@ void main() {
);
});
group('ShareScoreRequested', () {
blocTest<BackboxBloc, BackboxState>(
'emits ShareState',
setUp: () {
leaderboardRepository = _MockLeaderboardRepository();
},
build: () => BackboxBloc(
leaderboardRepository: leaderboardRepository,
initialEntries: emptyEntries,
),
act: (bloc) => bloc.add(
ShareScoreRequested(score: 100),
),
expect: () => [
ShareState(score: 100),
],
);
});
group('LeaderboardRequested', () {
blocTest<BackboxBloc, BackboxState>(
'adds [LoadingState, LeaderboardSuccessState] when request succeeds',

@ -203,5 +203,23 @@ void main() {
});
});
});
group('ShareState', () {
test('can be instantiated', () {
expect(
ShareState(score: 0),
isNotNull,
);
});
test('supports value comparison', () {
expect(
ShareState(score: 0),
equals(
ShareState(score: 0),
),
);
});
});
});
}

@ -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);
},
);
});
}

@ -14,6 +14,7 @@ import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
import 'package:share_repository/share_repository.dart';
class _TestGame extends Forge2DGame {
@override
@ -65,6 +66,8 @@ class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
class _MockShareRepository extends Mock implements ShareRepository {}
class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get score => '';
@ -149,9 +152,11 @@ void main() {
'changes the backbox display',
(game) async {
final component = GameBlocStatusListener();
final repository = _MockLeaderboardRepository();
final leaderboardRepository = _MockLeaderboardRepository();
final shareRepository = _MockShareRepository();
final backbox = Backbox(
leaderboardRepository: repository,
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
entries: const [],
);
@ -165,9 +170,11 @@ void main() {
'removes FlipperKeyControllingBehavior from Flipper',
(game) async {
final component = GameBlocStatusListener();
final repository = _MockLeaderboardRepository();
final leaderboardRepository = _MockLeaderboardRepository();
final shareRepository = _MockShareRepository();
final backbox = Backbox(
leaderboardRepository: repository,
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
entries: const [],
);
final flipper = Flipper.test(side: BoardSide.left);
@ -193,9 +200,11 @@ void main() {
(game) async {
final audioPlayer = _MockPinballAudioPlayer();
final component = GameBlocStatusListener();
final repository = _MockLeaderboardRepository();
final leaderboardRepository = _MockLeaderboardRepository();
final shareRepository = _MockShareRepository();
final backbox = Backbox(
leaderboardRepository: repository,
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
entries: const [],
);
await game.pump(
@ -245,9 +254,11 @@ void main() {
'adds key controlling behavior to Flippers when the game is started',
(game) async {
final component = GameBlocStatusListener();
final repository = _MockLeaderboardRepository();
final leaderboardRepository = _MockLeaderboardRepository();
final shareRepository = _MockShareRepository();
final backbox = Backbox(
leaderboardRepository: repository,
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
entries: const [],
);
final flipper = Flipper.test(side: BoardSide.left);

@ -16,12 +16,14 @@ import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_audio/src/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:share_repository/share_repository.dart';
class _TestPinballGame extends PinballGame {
_TestPinballGame()
: super(
characterThemeBloc: CharacterThemeCubit(),
leaderboardRepository: _MockLeaderboardRepository(),
shareRepository: _MockShareRepository(),
gameBloc: GameBloc(),
l10n: _MockAppLocalizations(),
audioPlayer: _MockPinballAudioPlayer(),
@ -41,6 +43,7 @@ class _TestDebugPinballGame extends DebugPinballGame {
: super(
characterThemeBloc: CharacterThemeCubit(),
leaderboardRepository: _MockLeaderboardRepository(),
shareRepository: _MockShareRepository(),
gameBloc: GameBloc(),
l10n: _MockAppLocalizations(),
audioPlayer: _MockPinballAudioPlayer(),
@ -81,6 +84,8 @@ class _MockDragEndInfo extends Mock implements DragEndInfo {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
class _MockShareRepository extends Mock implements ShareRepository {}
class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {}
void main() {

@ -16,6 +16,7 @@ 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:share_repository/share_repository.dart';
import '../../helpers/helpers.dart';
@ -24,6 +25,7 @@ class _TestPinballGame extends PinballGame {
: super(
characterThemeBloc: CharacterThemeCubit(),
leaderboardRepository: _MockLeaderboardRepository(),
shareRepository: _MockShareRepository(),
gameBloc: GameBloc(),
l10n: _MockAppLocalizations(),
audioPlayer: _MockPinballAudioPlayer(),
@ -60,6 +62,8 @@ class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
class _MockShareRepository extends Mock implements ShareRepository {}
void main() {
final game = _TestPinballGame();

@ -12,12 +12,15 @@ 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 _MockAssetsManagerCubit extends Mock implements AssetsManagerCubit {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
class _MockShareRepository extends Mock implements ShareRepository {}
class _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {}
class _MockGameBloc extends Mock implements GameBloc {}
@ -55,6 +58,7 @@ extension PumpApp on WidgetTester {
AssetsManagerCubit? assetsManagerCubit,
CharacterThemeCubit? characterThemeCubit,
LeaderboardRepository? leaderboardRepository,
ShareRepository? shareRepository,
PinballAudioPlayer? pinballAudioPlayer,
}) {
return runAsync(() {
@ -64,6 +68,9 @@ extension PumpApp on WidgetTester {
RepositoryProvider.value(
value: leaderboardRepository ?? _MockLeaderboardRepository(),
),
RepositoryProvider.value(
value: shareRepository ?? _MockShareRepository(),
),
RepositoryProvider.value(
value: pinballAudioPlayer ?? _buildDefaultPinballAudioPlayer(),
),

Loading…
Cancel
Save