diff --git a/lib/game/components/backbox/displays/displays.dart b/lib/game/components/backbox/displays/displays.dart index a516587d..07284ece 100644 --- a/lib/game/components/backbox/displays/displays.dart +++ b/lib/game/components/backbox/displays/displays.dart @@ -2,3 +2,4 @@ export 'initials_input_display.dart'; export 'initials_submission_failure_display.dart'; export 'initials_submission_success_display.dart'; export 'loading_display.dart'; +export 'share_display.dart'; diff --git a/lib/game/components/backbox/displays/share_display.dart b/lib/game/components/backbox/displays/share_display.dart new file mode 100644 index 00000000..621ea5ea --- /dev/null +++ b/lib/game/components/backbox/displays/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 used tries to share the 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 to share score on 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 onLoad() async { + await super.onLoad(); + text = readProvider().letEveryone; + } +} + +class _SharingYourScoreTextComponent extends TextComponent with HasGameRef { + _SharingYourScoreTextComponent() + : super( + anchor: Anchor.center, + position: Vector2(0, 2.5), + textRenderer: _descriptionTextPaint, + ); + + @override + Future onLoad() async { + await super.onLoad(); + text = readProvider().bySharingYourScore; + } +} + +class _SocialMediaTextComponent extends TextComponent with HasGameRef { + _SocialMediaTextComponent() + : super( + anchor: Anchor.center, + position: Vector2(0, 5), + textRenderer: _descriptionTextPaint, + ); + + @override + Future onLoad() async { + await super.onLoad(); + text = readProvider().socialMediaAccount; + } +} + +class _SocialNetworksComponent extends PositionComponent with HasGameRef { + _SocialNetworksComponent({ + OnSocialShareTap? onShare, + }) : super( + anchor: Anchor.center, + position: Vector2(0, 10), + 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 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 onLoad() async { + await super.onLoad(); + final sprite = Sprite( + gameRef.images.fromCache(Assets.images.backbox.button.twitter.keyName), + ); + this.sprite = sprite; + size = sprite.originalSize / 25; + } +} diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index ac324417..537cc6e2 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -100,6 +100,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.googleWord.letter1.lit.keyName), images.load(components.Assets.images.googleWord.letter1.dimmed.keyName), images.load(components.Assets.images.googleWord.letter2.lit.keyName), diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index b4886e4c..b0db048c 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -17,7 +17,7 @@ import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart'; class PinballGame extends PinballForge2DGame - with HasKeyboardHandlerComponents, MultiTouchTapDetector { + with HasKeyboardHandlerComponents, MultiTouchTapDetector, HasTappables { PinballGame({ required CharacterTheme characterTheme, required this.leaderboardRepository, diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 03fde0bd..ca3caff9 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -128,4 +128,17 @@ "@ioPinball": { "description": "I/O Pinball - Name of the game" } + , + "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" + } } diff --git a/packages/pinball_components/assets/images/backbox/button/facebook.png b/packages/pinball_components/assets/images/backbox/button/facebook.png new file mode 100644 index 00000000..f6d29ab2 Binary files /dev/null and b/packages/pinball_components/assets/images/backbox/button/facebook.png differ diff --git a/packages/pinball_components/assets/images/backbox/button/twitter.png b/packages/pinball_components/assets/images/backbox/button/twitter.png new file mode 100644 index 00000000..f109a4b8 Binary files /dev/null and b/packages/pinball_components/assets/images/backbox/button/twitter.png differ diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index cac04cc0..e31b7db9 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -54,6 +54,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'); @@ -66,9 +69,6 @@ class $AssetsImagesBackboxGen { class $AssetsImagesBallGen { const $AssetsImagesBallGen(); - /// File path: assets/images/ball/ball.png - AssetGenImage get ball => const AssetGenImage('assets/images/ball/ball.png'); - /// File path: assets/images/ball/flame_effect.png AssetGenImage get flameEffect => const AssetGenImage('assets/images/ball/flame_effect.png'); @@ -380,6 +380,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(); diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index 4f66c220..1876e5aa 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -88,6 +88,7 @@ flutter: - assets/images/multiplier/x6/ - assets/images/score/ - assets/images/backbox/ + - assets/images/backbox/button/ - assets/images/flapper/ - assets/images/skill_shot/ diff --git a/packages/pinball_components/sandbox/pubspec.lock b/packages/pinball_components/sandbox/pubspec.lock index d2500fbe..5ac4a08e 100644 --- a/packages/pinball_components/sandbox/pubspec.lock +++ b/packages/pinball_components/sandbox/pubspec.lock @@ -291,6 +291,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.2.4" + share_repository: + dependency: transitive + description: + path: "../../share_repository" + relative: true + source: path + version: "1.0.0+1" shared_preferences: dependency: transitive description: diff --git a/pubspec.lock b/pubspec.lock index 96f9f2a6..f5c98153 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -576,6 +576,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + share_repository: + dependency: "direct main" + description: + path: "packages/share_repository" + relative: true + source: path + version: "1.0.0+1" shelf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index fcee1e6e..536d9e91 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,8 @@ dependencies: path: packages/pinball_ui platform_helper: path: packages/platform_helper + share_repository: + path: packages/share_repository dev_dependencies: bloc_test: ^9.0.2 diff --git a/test/game/components/backbox/displays/share_display_test.dart b/test/game/components/backbox/displays/share_display_test.dart new file mode 100644 index 00000000..cfc59407 --- /dev/null +++ b/test/game/components/backbox/displays/share_display_test.dart @@ -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 onLoad() async { + await super.onLoad(); + images.prefix = ''; + await images.loadAll( + [ + Assets.images.backbox.button.facebook.keyName, + Assets.images.backbox.button.twitter.keyName, + ], + ); + } + + Future pump(ShareDisplay component) { + return ensureAdd( + FlameBlocProvider.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 tap on Facebook button', + (game) async { + var tapped = false; + + final tapDownInfo = _MockTapDownInfo(); + final component = ShareDisplay( + onShare: (_) => tapped = true, + ); + await game.pump(component); + + final facebookButton = + component.descendants().whereType().first; + + facebookButton.onTapDown(tapDownInfo); + + expect(tapped, isTrue); + }, + ); + + flameTester.test( + 'calls onShare when tap on Twitter button', + (game) async { + var tapped = false; + + final tapDownInfo = _MockTapDownInfo(); + final component = ShareDisplay( + onShare: (_) => tapped = true, + ); + await game.pump(component); + + final twitterButton = + component.descendants().whereType().first; + + twitterButton.onTapDown(tapDownInfo); + + expect(tapped, isTrue); + }, + ); + }); +}