diff --git a/lib/footer/footer.dart b/lib/footer/footer.dart new file mode 100644 index 00000000..711d1ba8 --- /dev/null +++ b/lib/footer/footer.dart @@ -0,0 +1,77 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/theme/theme.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +/// {@template footer} +/// Footer widget with links to the main tech stack. +/// {@endtemplate} +class Footer extends StatelessWidget { + /// {@macro footer} + const Footer({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.fromLTRB(50, 0, 50, 32), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: const [ + _MadeWithFlutterAndFirebase(), + _GoogleIO(), + ], + ), + ); + } +} + +class _GoogleIO extends StatelessWidget { + const _GoogleIO({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final theme = Theme.of(context); + return Text( + l10n.footerGoogleIOText, + style: theme.textTheme.bodyText1!.copyWith(color: AppColors.white), + ); + } +} + +class _MadeWithFlutterAndFirebase extends StatelessWidget { + const _MadeWithFlutterAndFirebase({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final theme = Theme.of(context); + return RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: l10n.footerMadeWithText, + style: theme.textTheme.bodyText1!.copyWith(color: AppColors.white), + children: [ + TextSpan( + text: l10n.footerFlutterLinkText, + recognizer: TapGestureRecognizer() + ..onTap = () => openLink('https://flutter.dev'), + style: const TextStyle( + decoration: TextDecoration.underline, + ), + ), + const TextSpan(text: ' & '), + TextSpan( + text: l10n.footerFirebaseLinkText, + recognizer: TapGestureRecognizer() + ..onTap = () => openLink('https://firebase.google.com'), + style: const TextStyle( + decoration: TextDecoration.underline, + ), + ), + ], + ), + ); + } +} diff --git a/lib/game/components/android_acres.dart b/lib/game/components/android_acres.dart index da8e4949..2af7335f 100644 --- a/lib/game/components/android_acres.dart +++ b/lib/game/components/android_acres.dart @@ -23,7 +23,7 @@ class AndroidAcres extends Blueprint { children: [ ScoringBehavior(points: 20000), ], - )..initialPosition = Vector2(-32.6, -9.2), + )..initialPosition = Vector2(-32.8, -9.2), AndroidBumper.cow( children: [ ScoringBehavior(points: 20), diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index ac5802bc..980343f2 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -13,6 +13,7 @@ extension PinballGameAssetsX on PinballGame { const dinoTheme = DinoTheme(); return [ + images.load(components.Assets.images.boardBackground.keyName), images.load(components.Assets.images.ball.ball.keyName), images.load(components.Assets.images.ball.flameEffect.keyName), images.load(components.Assets.images.signpost.inactive.keyName), diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index f80db4c5..ace765f0 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -9,11 +9,10 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:pinball/game/game.dart'; -import 'package:pinball/gen/assets.gen.dart'; import 'package:pinball_audio/pinball_audio.dart'; -import 'package:pinball_components/pinball_components.dart' hide Assets; +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_theme/pinball_theme.dart'; class PinballGame extends Forge2DGame with @@ -46,6 +45,7 @@ class PinballGame extends Forge2DGame unawaited(add(gameFlowController = GameFlowController(this))); unawaited(add(CameraController(this))); unawaited(add(Backboard.waiting(position: Vector2(0, -88)))); + await add(BoardBackgroundSpriteComponent()); await add(Drain()); await add(BottomGroup()); unawaited(addFromBlueprint(Boundaries())); @@ -186,26 +186,25 @@ class DebugPinballGame extends PinballGame with FPSCounter { @override Future onLoad() async { await super.onLoad(); - await _loadBackground(); await add(_DebugInformation()); } - // TODO(alestiago): Move to PinballGame once we have the real background - // component. - Future _loadBackground() async { - final sprite = await loadSprite( - Assets.images.components.background.path, - ); - final spriteComponent = SpriteComponent( - sprite: sprite, - size: Vector2(120, 160), - anchor: Anchor.center, - ) - ..position = Vector2(0, -7.8) - ..priority = RenderPriority.background; - - await add(spriteComponent); - } + // TODO(allisonryan0002): Remove after google letters have been correctly + // placed. + // Future _loadBackground() async { + // final sprite = await loadSprite( + // Assets.images.components.background.path, + // ); + // final spriteComponent = SpriteComponent( + // sprite: sprite, + // size: Vector2(120, 160), + // anchor: Anchor.center, + // ) + // ..position = Vector2(0, -7.8) + // ..priority = RenderPriority.boardBackground; + + // await add(spriteComponent); + // } @override void onTapUp(TapUpInfo info) { diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 19b12296..7691e2dd 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -107,5 +107,21 @@ "rounds": "Ball Ct:", "@rounds": { "description": "Text displayed on the scoreboard widget to indicate rounds left" + }, + "footerMadeWithText": "Made with ", + "@footerMadeWithText": { + "description": "Text shown on the footer which mentions technologies used to build the app." + }, + "footerFlutterLinkText": "Flutter", + "@footerFlutterLinkText": { + "description": "Text on the link shown on the footer which navigates to the Flutter page" + }, + "footerFirebaseLinkText": "Firebase", + "@footerFirebaseLinkText": { + "description": "Text on the link shown on the footer which navigates to the Firebase page" + }, + "footerGoogleIOText": "Google I/O", + "@footerGoogleIOText": { + "description": "Text shown on the footer which mentions Google I/O" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_es.arb b/lib/l10n/arb/app_es.arb deleted file mode 100644 index 597a39d8..00000000 --- a/lib/l10n/arb/app_es.arb +++ /dev/null @@ -1,15 +0,0 @@ -{ - "@@locale": "es", - "play": "Jugar", - "@play": { - "description": "Text displayed on the landing page play button" - }, - "start": "Comienzo", - "@start": { - "description": "Text displayed on the character selection page start button" - }, - "characterSelectionTitle": "¡Elige a tu personaje!", - "@characterSelectionTitle": { - "description": "Title text displayed on the character selection page" - } -} \ No newline at end of file diff --git a/packages/pinball_components/assets/images/board-background.png b/packages/pinball_components/assets/images/board-background.png new file mode 100644 index 00000000..979a0873 Binary files /dev/null and b/packages/pinball_components/assets/images/board-background.png differ diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index 74124c02..3a81e08d 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -14,6 +14,10 @@ class $AssetsImagesGen { $AssetsImagesBackboardGen get backboard => const $AssetsImagesBackboardGen(); $AssetsImagesBallGen get ball => const $AssetsImagesBallGen(); $AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen(); + + /// File path: assets/images/board-background.png + AssetGenImage get boardBackground => + const AssetGenImage('assets/images/board-background.png'); $AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen(); $AssetsImagesDashGen get dash => const $AssetsImagesDashGen(); $AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen(); diff --git a/packages/pinball_components/lib/src/components/board_background_sprite_component.dart b/packages/pinball_components/lib/src/components/board_background_sprite_component.dart new file mode 100644 index 00000000..1a4e34c6 --- /dev/null +++ b/packages/pinball_components/lib/src/components/board_background_sprite_component.dart @@ -0,0 +1,26 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flame/components.dart'; +import 'package:pinball_components/pinball_components.dart'; + +class BoardBackgroundSpriteComponent extends SpriteComponent with HasGameRef { + BoardBackgroundSpriteComponent() + : super( + anchor: Anchor.center, + priority: RenderPriority.boardBackground, + position: Vector2(0, -1), + ); + + @override + Future onLoad() async { + await super.onLoad(); + + final sprite = Sprite( + gameRef.images.fromCache( + Assets.images.boardBackground.keyName, + ), + ); + this.sprite = sprite; + size = sprite.originalSize / 10; + } +} diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index fe26276d..b5a373a4 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -3,6 +3,7 @@ export 'android_spaceship.dart'; export 'backboard/backboard.dart'; export 'ball.dart'; export 'baseboard.dart'; +export 'board_background_sprite_component.dart'; export 'board_dimensions.dart'; export 'board_side.dart'; export 'boundaries.dart'; diff --git a/packages/pinball_components/lib/src/components/render_priority.dart b/packages/pinball_components/lib/src/components/render_priority.dart index 3e7d5a29..2359229f 100644 --- a/packages/pinball_components/lib/src/components/render_priority.dart +++ b/packages/pinball_components/lib/src/components/render_priority.dart @@ -33,13 +33,13 @@ abstract class RenderPriority { // TODO(allisonryan0002): fix this magic priority. Could bump all priorities // so there are no negatives. - static const int background = 3 * _below + _base; + static const int boardBackground = 3 * _below + _base; // Boundaries static const int bottomBoundary = _above + dinoBottomWall; - static const int outerBoundary = _above + background; + static const int outerBoundary = _above + boardBackground; static const int outerBottomBoundary = _above + rocket; diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index cd5b8482..cadf7057 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -45,6 +45,7 @@ flutter: - asset: fonts/PixeloidMono-1G8ae.ttf assets: + - assets/images/ - assets/images/ball/ - assets/images/baseboard/ - assets/images/boundary/ diff --git a/packages/pinball_components/test/src/components/board_background_sprite_component_test.dart b/packages/pinball_components/test/src/components/board_background_sprite_component_test.dart new file mode 100644 index 00000000..df35594f --- /dev/null +++ b/packages/pinball_components/test/src/components/board_background_sprite_component_test.dart @@ -0,0 +1,48 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + Assets.images.boardBackground.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); + + group('BoardBackgroundSpriteComponent', () { + flameTester.test( + 'loads correctly', + (game) async { + final boardBackground = BoardBackgroundSpriteComponent(); + await game.ensureAdd(boardBackground); + + expect(game.contains(boardBackground), isTrue); + }, + ); + + flameTester.testGameWidget( + 'renders correctly', + setUp: (game, tester) async { + await game.images.loadAll(assets); + final boardBackground = BoardBackgroundSpriteComponent(); + await game.ensureAdd(boardBackground); + await tester.pump(); + + game.camera + ..followVector2(Vector2.zero()) + ..zoom = 3.7; + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/board-background.png'), + ); + }, + ); + }); +} diff --git a/packages/pinball_components/test/src/components/golden/board-background.png b/packages/pinball_components/test/src/components/golden/board-background.png new file mode 100644 index 00000000..789c5465 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/board-background.png differ diff --git a/packages/pinball_ui/lib/pinball_ui.dart b/packages/pinball_ui/lib/pinball_ui.dart index b46adf95..6e139280 100644 --- a/packages/pinball_ui/lib/pinball_ui.dart +++ b/packages/pinball_ui/lib/pinball_ui.dart @@ -1,3 +1,7 @@ library pinball_ui; +export 'package:url_launcher/url_launcher.dart'; +export 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; + export 'src/dialog/dialog.dart'; +export 'src/external_links/external_links.dart'; diff --git a/packages/pinball_ui/lib/src/external_links/external_links.dart b/packages/pinball_ui/lib/src/external_links/external_links.dart new file mode 100644 index 00000000..8e4792ea --- /dev/null +++ b/packages/pinball_ui/lib/src/external_links/external_links.dart @@ -0,0 +1,12 @@ +import 'package:flutter/foundation.dart'; +import 'package:url_launcher/url_launcher.dart'; + +/// Opens the given [url] in a new tab of the host browser +Future openLink(String url, {VoidCallback? onError}) async { + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); + } else if (onError != null) { + onError(); + } +} diff --git a/packages/pinball_ui/pubspec.yaml b/packages/pinball_ui/pubspec.yaml index 79c65338..60f32207 100644 --- a/packages/pinball_ui/pubspec.yaml +++ b/packages/pinball_ui/pubspec.yaml @@ -9,17 +9,18 @@ environment: dependencies: flutter: sdk: flutter + url_launcher: ^6.1.0 dev_dependencies: flutter_test: sdk: flutter + mocktail: ^0.3.0 test: ^1.19.2 very_good_analysis: ^2.4.0 flutter: uses-material-design: true generate: true - assets: - assets/images/dialog/ diff --git a/packages/pinball_ui/test/src/external_links/external_links_test.dart b/packages/pinball_ui/test/src/external_links/external_links_test.dart new file mode 100644 index 00000000..83cc2d63 --- /dev/null +++ b/packages/pinball_ui/test/src/external_links/external_links_test.dart @@ -0,0 +1,81 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball_ui/pinball_ui.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class MockUrlLauncher extends Mock + with MockPlatformInterfaceMixin + implements UrlLauncherPlatform {} + +void main() { + late UrlLauncherPlatform urlLauncher; + + setUp(() { + urlLauncher = MockUrlLauncher(); + UrlLauncherPlatform.instance = urlLauncher; + }); + + group('openLink', () { + test('launches the link', () 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, + ); + await openLink('uri'); + verify( + () => 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'), + ), + ); + }); + + test('executes the onError callback when it cannot launch', () async { + var wasCalled = false; + when( + () => urlLauncher.canLaunch(any()), + ).thenAnswer( + (_) async => false, + ); + 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, + ); + await openLink( + 'url', + onError: () { + wasCalled = true; + }, + ); + await expectLater(wasCalled, isTrue); + }); + }); +} diff --git a/pubspec.lock b/pubspec.lock index 3b6d5d63..3e73f15f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -700,6 +700,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.16" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.15" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" uuid: dependency: transitive description: @@ -772,4 +828,4 @@ packages: version: "3.1.0" sdks: dart: ">=2.16.0 <3.0.0" - flutter: ">=2.8.0" + flutter: ">=2.10.0" diff --git a/test/footer/footer_test.dart b/test/footer/footer_test.dart new file mode 100644 index 00000000..c18d76e7 --- /dev/null +++ b/test/footer/footer_test.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/footer/footer.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +import '../helpers/helpers.dart'; + +void main() { + group('Footer', () { + late UrlLauncherPlatform urlLauncher; + + setUp(() async { + urlLauncher = MockUrlLauncher(); + UrlLauncherPlatform.instance = urlLauncher; + }); + testWidgets('renders "Made with..." and "Google I/O"', (tester) async { + await tester.pumpApp(const Footer()); + expect(find.text('Google I/O'), findsOneWidget); + expect( + find.byWidgetPredicate( + (widget) => + widget is RichText && + widget.text.toPlainText() == 'Made with Flutter & Firebase', + ), + findsOneWidget, + ); + }); + + testWidgets( + 'tapping on "Flutter" opens the flutter website', + (tester) 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); + await tester.pumpApp(const Footer()); + final flutterTextFinder = find.byWidgetPredicate( + (widget) => widget is RichText && tapTextSpan(widget, 'Flutter'), + ); + await tester.tap(flutterTextFinder); + await tester.pumpAndSettle(); + verify( + () => urlLauncher.launch( + 'https://flutter.dev', + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ); + }, + ); + + testWidgets( + 'tapping on "Firebase" opens the firebase website', + (tester) 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); + await tester.pumpApp(const Footer()); + final firebaseTextFinder = find.byWidgetPredicate( + (widget) => widget is RichText && tapTextSpan(widget, 'Firebase'), + ); + await tester.tap(firebaseTextFinder); + await tester.pumpAndSettle(); + verify( + () => urlLauncher.launch( + 'https://firebase.google.com', + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ); + }, + ); + }); +} diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index 809d2d61..9a64388c 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -24,6 +24,7 @@ void main() { Assets.images.backboard.backboardScores.keyName, Assets.images.backboard.backboardGameOver.keyName, Assets.images.backboard.display.keyName, + Assets.images.boardBackground.keyName, Assets.images.ball.ball.keyName, Assets.images.ball.flameEffect.keyName, Assets.images.baseboard.left.keyName, diff --git a/test/helpers/helpers.dart b/test/helpers/helpers.dart index 8732035a..58b4b126 100644 --- a/test/helpers/helpers.dart +++ b/test/helpers/helpers.dart @@ -12,3 +12,4 @@ export 'mocks.dart'; export 'navigator.dart'; export 'pump_app.dart'; export 'test_games.dart'; +export 'text_span.dart'; diff --git a/test/helpers/mocks.dart b/test/helpers/mocks.dart index e2e8162b..f357a699 100644 --- a/test/helpers/mocks.dart +++ b/test/helpers/mocks.dart @@ -11,6 +11,8 @@ 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_components/pinball_components.dart'; +import 'package:pinball_ui/pinball_ui.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; class MockPinballGame extends Mock implements PinballGame {} @@ -92,3 +94,7 @@ class MockSparkyBumper extends Mock implements SparkyBumper {} class MockMultiballCubit extends Mock implements MultiballCubit {} class MockMultiplierCubit extends Mock implements MultiplierCubit {} + +class MockUrlLauncher extends Mock + with MockPlatformInterfaceMixin + implements UrlLauncherPlatform {} diff --git a/test/helpers/text_span.dart b/test/helpers/text_span.dart new file mode 100644 index 00000000..c98d33d9 --- /dev/null +++ b/test/helpers/text_span.dart @@ -0,0 +1,17 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/widgets.dart'; + +bool tapTextSpan(RichText richText, String text) { + final isTapped = !richText.text.visitChildren( + (visitor) => _findTextAndTap(visitor, text), + ); + return isTapped; +} + +bool _findTextAndTap(InlineSpan visitor, String text) { + if (visitor is TextSpan && visitor.text == text) { + (visitor.recognizer as TapGestureRecognizer?)?.onTap?.call(); + return false; + } + return true; +} diff --git a/web/index.html b/web/index.html index e1544bf2..68e1e2a0 100644 --- a/web/index.html +++ b/web/index.html @@ -56,12 +56,20 @@ I/O Pinball Machine - Flutter - +