Merge branch 'main' into feat/dino-mechanics

pull/277/head
Allison Ryan 3 years ago
commit f6cb66f17b

@ -0,0 +1,22 @@
name: authentication_repository
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
paths:
- "packages/authentication_repository/**"
- ".github/workflows/authentication_repository.yaml"
pull_request:
paths:
- "packages/authentication_repository/**"
- ".github/workflows/authentication_repository.yaml"
jobs:
build:
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
with:
working_directory: packages/authentication_repository

@ -7,6 +7,7 @@
// ignore_for_file: public_member_api_docs // ignore_for_file: public_member_api_docs
import 'package:authentication_repository/authentication_repository.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
@ -15,16 +16,20 @@ import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_ui/pinball_ui.dart';
class App extends StatelessWidget { class App extends StatelessWidget {
const App({ const App({
Key? key, Key? key,
required AuthenticationRepository authenticationRepository,
required LeaderboardRepository leaderboardRepository, required LeaderboardRepository leaderboardRepository,
required PinballAudio pinballAudio, required PinballAudio pinballAudio,
}) : _leaderboardRepository = leaderboardRepository, }) : _authenticationRepository = authenticationRepository,
_leaderboardRepository = leaderboardRepository,
_pinballAudio = pinballAudio, _pinballAudio = pinballAudio,
super(key: key); super(key: key);
final AuthenticationRepository _authenticationRepository;
final LeaderboardRepository _leaderboardRepository; final LeaderboardRepository _leaderboardRepository;
final PinballAudio _pinballAudio; final PinballAudio _pinballAudio;
@ -32,19 +37,21 @@ class App extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiRepositoryProvider( return MultiRepositoryProvider(
providers: [ providers: [
RepositoryProvider.value(value: _authenticationRepository),
RepositoryProvider.value(value: _leaderboardRepository), RepositoryProvider.value(value: _leaderboardRepository),
RepositoryProvider.value(value: _pinballAudio), RepositoryProvider.value(value: _pinballAudio),
], ],
child: BlocProvider( child: BlocProvider(
create: (context) => CharacterThemeCubit(), create: (context) => CharacterThemeCubit(),
child: const MaterialApp( child: MaterialApp(
title: 'I/O Pinball', title: 'I/O Pinball',
localizationsDelegates: [ theme: PinballTheme.standard,
localizationsDelegates: const [
AppLocalizations.delegate, AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,
], ],
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
home: PinballGamePage(), home: const PinballGamePage(),
), ),
), ),
); );

@ -12,6 +12,7 @@ import 'dart:developer';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
class AppBlocObserver extends BlocObserver { class AppBlocObserver extends BlocObserver {
@ -28,9 +29,12 @@ class AppBlocObserver extends BlocObserver {
} }
} }
Future<void> bootstrap( typedef BootstrapBuilder = Future<Widget> Function(
Future<Widget> Function(FirebaseFirestore firestore) builder, FirebaseFirestore firestore,
) async { FirebaseAuth firebaseAuth,
);
Future<void> bootstrap(BootstrapBuilder builder) async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = (details) { FlutterError.onError = (details) {
log(details.exceptionAsString(), stackTrace: details.stack); log(details.exceptionAsString(), stackTrace: details.stack);
@ -39,7 +43,12 @@ Future<void> bootstrap(
await runZonedGuarded( await runZonedGuarded(
() async { () async {
await BlocOverrides.runZoned( await BlocOverrides.runZoned(
() async => runApp(await builder(FirebaseFirestore.instance)), () async => runApp(
await builder(
FirebaseFirestore.instance,
FirebaseAuth.instance,
),
),
blocObserver: AppBlocObserver(), blocObserver: AppBlocObserver(),
); );
}, },

@ -0,0 +1,76 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:pinball/l10n/l10n.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: PinballColors.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: PinballColors.white),
children: <TextSpan>[
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,
),
),
],
),
);
}
}

@ -23,7 +23,7 @@ class AndroidAcres extends Blueprint {
children: [ children: [
ScoringBehavior(points: 20000), ScoringBehavior(points: 20000),
], ],
)..initialPosition = Vector2(-32.6, -9.2), )..initialPosition = Vector2(-32.8, -9.2),
AndroidBumper.cow( AndroidBumper.cow(
children: [ children: [
ScoringBehavior(points: 20), ScoringBehavior(points: 20),

@ -12,4 +12,4 @@ export 'google_word/google_word.dart';
export 'launcher.dart'; export 'launcher.dart';
export 'multipliers/multipliers.dart'; export 'multipliers/multipliers.dart';
export 'scoring_behavior.dart'; export 'scoring_behavior.dart';
export 'sparky_fire_zone.dart'; export 'sparky_scorch.dart';

@ -5,15 +5,13 @@ import 'package:pinball/game/game.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';
/// {@template sparky_fire_zone} /// {@template sparky_scorch}
/// Area positioned at the top left of the board where the [Ball] /// Area positioned at the top left of the board containing the
/// can bounce off [SparkyBumper]s. /// [SparkyComputer], [SparkyAnimatronic], and [SparkyBumper]s.
///
/// When a [Ball] hits [SparkyBumper]s, the bumper animates.
/// {@endtemplate} /// {@endtemplate}
class SparkyFireZone extends Blueprint { class SparkyScorch extends Blueprint {
/// {@macro sparky_fire_zone} /// {@macro sparky_scorch}
SparkyFireZone() SparkyScorch()
: super( : super(
components: [ components: [
SparkyBumper.a( SparkyBumper.a(

@ -13,6 +13,7 @@ extension PinballGameAssetsX on PinballGame {
const dinoTheme = DinoTheme(); const dinoTheme = DinoTheme();
return [ return [
images.load(components.Assets.images.boardBackground.keyName),
images.load(components.Assets.images.ball.ball.keyName), images.load(components.Assets.images.ball.ball.keyName),
images.load(components.Assets.images.ball.flameEffect.keyName), images.load(components.Assets.images.ball.flameEffect.keyName),
images.load(components.Assets.images.signpost.inactive.keyName), images.load(components.Assets.images.signpost.inactive.keyName),
@ -88,13 +89,14 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.android.bumper.cow.dimmed.keyName), images.load(components.Assets.images.android.bumper.cow.dimmed.keyName),
images.load(components.Assets.images.sparky.computer.top.keyName), images.load(components.Assets.images.sparky.computer.top.keyName),
images.load(components.Assets.images.sparky.computer.base.keyName), images.load(components.Assets.images.sparky.computer.base.keyName),
images.load(components.Assets.images.sparky.computer.glow.keyName),
images.load(components.Assets.images.sparky.animatronic.keyName), images.load(components.Assets.images.sparky.animatronic.keyName),
images.load(components.Assets.images.sparky.bumper.a.inactive.keyName), images.load(components.Assets.images.sparky.bumper.a.lit.keyName),
images.load(components.Assets.images.sparky.bumper.a.active.keyName), images.load(components.Assets.images.sparky.bumper.a.dimmed.keyName),
images.load(components.Assets.images.sparky.bumper.b.active.keyName), images.load(components.Assets.images.sparky.bumper.b.lit.keyName),
images.load(components.Assets.images.sparky.bumper.b.inactive.keyName), images.load(components.Assets.images.sparky.bumper.b.dimmed.keyName),
images.load(components.Assets.images.sparky.bumper.c.active.keyName), images.load(components.Assets.images.sparky.bumper.c.lit.keyName),
images.load(components.Assets.images.sparky.bumper.c.inactive.keyName), images.load(components.Assets.images.sparky.bumper.c.dimmed.keyName),
images.load(components.Assets.images.backboard.backboardScores.keyName), images.load(components.Assets.images.backboard.backboardScores.keyName),
images.load(components.Assets.images.backboard.backboardGameOver.keyName), images.load(components.Assets.images.backboard.backboardGameOver.keyName),
images.load(components.Assets.images.googleWord.letter1.keyName), images.load(components.Assets.images.googleWord.letter1.keyName),

@ -9,11 +9,10 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/gen/assets.gen.dart';
import 'package:pinball_audio/pinball_audio.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_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' hide Assets; import 'package:pinball_theme/pinball_theme.dart';
class PinballGame extends Forge2DGame class PinballGame extends Forge2DGame
with with
@ -46,6 +45,7 @@ class PinballGame extends Forge2DGame
unawaited(add(gameFlowController = GameFlowController(this))); unawaited(add(gameFlowController = GameFlowController(this)));
unawaited(add(CameraController(this))); unawaited(add(CameraController(this)));
unawaited(add(Backboard.waiting(position: Vector2(0, -88)))); unawaited(add(Backboard.waiting(position: Vector2(0, -88))));
await add(BoardBackgroundSpriteComponent());
await add(Drain()); await add(Drain());
await add(BottomGroup()); await add(BottomGroup());
unawaited(addFromBlueprint(Boundaries())); unawaited(addFromBlueprint(Boundaries()));
@ -54,7 +54,7 @@ class PinballGame extends Forge2DGame
unawaited(addFromBlueprint(launcher)); unawaited(addFromBlueprint(launcher));
await add(Multipliers()); await add(Multipliers());
await add(FlutterForest()); await add(FlutterForest());
await addFromBlueprint(SparkyFireZone()); await addFromBlueprint(SparkyScorch());
await addFromBlueprint(AndroidAcres()); await addFromBlueprint(AndroidAcres());
await addFromBlueprint(DinoDesert()); await addFromBlueprint(DinoDesert());
unawaited(addFromBlueprint(Slingshots())); unawaited(addFromBlueprint(Slingshots()));
@ -185,26 +185,25 @@ class DebugPinballGame extends PinballGame with FPSCounter {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
await _loadBackground();
await add(_DebugInformation()); await add(_DebugInformation());
} }
// TODO(alestiago): Move to PinballGame once we have the real background // TODO(allisonryan0002): Remove after google letters have been correctly
// component. // placed.
Future<void> _loadBackground() async { // Future<void> _loadBackground() async {
final sprite = await loadSprite( // final sprite = await loadSprite(
Assets.images.components.background.path, // Assets.images.components.background.path,
); // );
final spriteComponent = SpriteComponent( // final spriteComponent = SpriteComponent(
sprite: sprite, // sprite: sprite,
size: Vector2(120, 160), // size: Vector2(120, 160),
anchor: Anchor.center, // anchor: Anchor.center,
) // )
..position = Vector2(0, -7.8) // ..position = Vector2(0, -7.8)
..priority = RenderPriority.background; // ..priority = RenderPriority.boardBackground;
await add(spriteComponent); // await add(spriteComponent);
} // }
@override @override
void onTapUp(TapUpInfo info) { void onTapUp(TapUpInfo info) {

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/gen/gen.dart'; import 'package:pinball/gen/gen.dart';
import 'package:pinball/theme/app_colors.dart'; import 'package:pinball_ui/pinball_ui.dart';
/// {@template game_hud} /// {@template game_hud}
/// Overlay on the [PinballGame]. /// Overlay on the [PinballGame].
@ -72,7 +72,7 @@ class _ScoreViewDecoration extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: radius, borderRadius: radius,
border: Border.all( border: Border.all(
color: AppColors.white, color: PinballColors.white,
width: borderWidth, width: borderWidth,
), ),
image: DecorationImage( image: DecorationImage(

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/theme/theme.dart'; import 'package:pinball_ui/pinball_ui.dart';
/// {@template round_count_display} /// {@template round_count_display}
/// Colored square indicating if a round is available. /// Colored square indicating if a round is available.
@ -20,9 +20,7 @@ class RoundCountDisplay extends StatelessWidget {
children: [ children: [
Text( Text(
l10n.rounds, l10n.rounds,
style: AppTextStyle.subtitle1.copyWith( style: Theme.of(context).textTheme.subtitle1,
color: AppColors.yellow,
),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Row( Row(
@ -53,9 +51,9 @@ class RoundIndicator extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final color = isActive ? AppColors.yellow : AppColors.yellow.withAlpha(128); final color =
isActive ? PinballColors.yellow : PinballColors.yellow.withAlpha(128);
const size = 8.0; const size = 8.0;
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: Container( child: Container(

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/theme/theme.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
/// {@template score_view} /// {@template score_view}
@ -38,9 +37,7 @@ class _GameOver extends StatelessWidget {
return Text( return Text(
l10n.gameOver, l10n.gameOver,
style: AppTextStyle.headline1.copyWith( style: Theme.of(context).textTheme.headline1,
color: AppColors.white,
),
); );
} }
} }
@ -58,9 +55,7 @@ class _ScoreDisplay extends StatelessWidget {
children: [ children: [
Text( Text(
l10n.score.toLowerCase(), l10n.score.toLowerCase(),
style: AppTextStyle.subtitle1.copyWith( style: Theme.of(context).textTheme.subtitle1,
color: AppColors.yellow,
),
), ),
const _ScoreText(), const _ScoreText(),
const RoundCountDisplay(), const RoundCountDisplay(),
@ -78,9 +73,7 @@ class _ScoreText extends StatelessWidget {
return Text( return Text(
score.formatScore(), score.formatScore(),
style: AppTextStyle.headline1.copyWith( style: Theme.of(context).textTheme.headline1,
color: AppColors.white,
),
); );
} }
} }

@ -107,5 +107,21 @@
"rounds": "Ball Ct:", "rounds": "Ball Ct:",
"@rounds": { "@rounds": {
"description": "Text displayed on the scoreboard widget to indicate rounds left" "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"
} }
} }

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

@ -5,16 +5,27 @@
// license that can be found in the LICENSE file or at // license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT. // https://opensource.org/licenses/MIT.
import 'dart:async';
import 'package:authentication_repository/authentication_repository.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart'; 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';
void main() { void main() {
bootstrap((firestore) async { bootstrap((firestore, firebaseAuth) async {
final leaderboardRepository = LeaderboardRepository(firestore); final leaderboardRepository = LeaderboardRepository(firestore);
final authenticationRepository = AuthenticationRepository(firebaseAuth);
final pinballAudio = PinballAudio(); final pinballAudio = PinballAudio();
unawaited(
Firebase.initializeApp().then(
(_) => authenticationRepository.authenticateAnonymously(),
),
);
return App( return App(
authenticationRepository: authenticationRepository,
leaderboardRepository: leaderboardRepository, leaderboardRepository: leaderboardRepository,
pinballAudio: pinballAudio, pinballAudio: pinballAudio,
); );

@ -5,16 +5,27 @@
// license that can be found in the LICENSE file or at // license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT. // https://opensource.org/licenses/MIT.
import 'dart:async';
import 'package:authentication_repository/authentication_repository.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart'; 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';
void main() { void main() {
bootstrap((firestore) async { bootstrap((firestore, firebaseAuth) async {
final leaderboardRepository = LeaderboardRepository(firestore); final leaderboardRepository = LeaderboardRepository(firestore);
final authenticationRepository = AuthenticationRepository(firebaseAuth);
final pinballAudio = PinballAudio(); final pinballAudio = PinballAudio();
unawaited(
Firebase.initializeApp().then(
(_) => authenticationRepository.authenticateAnonymously(),
),
);
return App( return App(
authenticationRepository: authenticationRepository,
leaderboardRepository: leaderboardRepository, leaderboardRepository: leaderboardRepository,
pinballAudio: pinballAudio, pinballAudio: pinballAudio,
); );

@ -5,16 +5,27 @@
// license that can be found in the LICENSE file or at // license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT. // https://opensource.org/licenses/MIT.
import 'dart:async';
import 'package:authentication_repository/authentication_repository.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart'; 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';
void main() { void main() {
bootstrap((firestore) async { bootstrap((firestore, firebaseAuth) async {
final leaderboardRepository = LeaderboardRepository(firestore); final leaderboardRepository = LeaderboardRepository(firestore);
final authenticationRepository = AuthenticationRepository(firebaseAuth);
final pinballAudio = PinballAudio(); final pinballAudio = PinballAudio();
unawaited(
Firebase.initializeApp().then(
(_) => authenticationRepository.authenticateAnonymously(),
),
);
return App( return App(
authenticationRepository: authenticationRepository,
leaderboardRepository: leaderboardRepository, leaderboardRepository: leaderboardRepository,
pinballAudio: pinballAudio, pinballAudio: pinballAudio,
); );

@ -5,7 +5,6 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/gen/gen.dart'; import 'package:pinball/gen/gen.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/theme/theme.dart';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
import 'package:platform_helper/platform_helper.dart'; import 'package:platform_helper/platform_helper.dart';
@ -122,7 +121,7 @@ class _MobileLaunchControls extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
const textStyle = AppTextStyle.subtitle3; final textStyle = Theme.of(context).textTheme.headline3;
return Column( return Column(
children: [ children: [
Text( Text(
@ -138,9 +137,7 @@ class _MobileLaunchControls extends StatelessWidget {
), ),
TextSpan( TextSpan(
text: l10n.launch, text: l10n.launch,
style: textStyle.copyWith( style: textStyle?.copyWith(color: PinballColors.blue),
color: AppColors.blue,
),
), ),
], ],
), ),
@ -156,7 +153,7 @@ class _MobileFlipperControls extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
const textStyle = AppTextStyle.subtitle3; final textStyle = Theme.of(context).textTheme.headline3;
return Column( return Column(
children: [ children: [
Text( Text(
@ -172,9 +169,7 @@ class _MobileFlipperControls extends StatelessWidget {
), ),
TextSpan( TextSpan(
text: l10n.flip, text: l10n.flip,
style: textStyle.copyWith( style: textStyle?.copyWith(color: PinballColors.orange),
color: AppColors.orange,
),
), ),
], ],
), ),
@ -207,21 +202,22 @@ class _HowToPlayHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
const headerTextStyle = AppTextStyle.title; final textStyle = Theme.of(context).textTheme.headline3?.copyWith(
color: PinballColors.darkBlue,
);
return FittedBox( return FittedBox(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
l10n.howToPlay, l10n.howToPlay,
style: headerTextStyle.copyWith( style: textStyle?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
Text( Text(
l10n.tipsForFlips, l10n.tipsForFlips,
style: headerTextStyle, style: textStyle,
), ),
], ],
), ),
@ -241,7 +237,7 @@ class _DesktopLaunchControls extends StatelessWidget {
children: [ children: [
Text( Text(
l10n.launchControls, l10n.launchControls,
style: AppTextStyle.headline4, style: Theme.of(context).textTheme.headline4,
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Wrap( Wrap(
@ -270,7 +266,7 @@ class _DesktopFlipperControls extends StatelessWidget {
children: [ children: [
Text( Text(
l10n.flipperControls, l10n.flipperControls,
style: AppTextStyle.subtitle2, style: Theme.of(context).textTheme.subtitle2,
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Column( Column(
@ -311,8 +307,9 @@ class KeyButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final textStyle = final textStyle =
_control.isArrow ? AppTextStyle.headline1 : AppTextStyle.headline3; _control.isArrow ? textTheme.headline1 : textTheme.headline3;
const height = 60.0; const height = 60.0;
final width = _control.isSpace ? height * 2.83 : height; final width = _control.isSpace ? height * 2.83 : height;
return DecoratedBox( return DecoratedBox(
@ -334,7 +331,7 @@ class KeyButton extends StatelessWidget {
quarterTurns: _control.isDown ? 1 : 0, quarterTurns: _control.isDown ? 1 : 0,
child: Text( child: Text(
_control.getCharacter(context), _control.getCharacter(context),
style: textStyle.copyWith(color: AppColors.white), style: textStyle?.copyWith(color: PinballColors.white),
), ),
), ),
), ),

@ -1,2 +0,0 @@
export 'app_colors.dart';
export 'app_text_style.dart';

@ -0,0 +1,39 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# VSCode related
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json

@ -0,0 +1,11 @@
# authentication_repository
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![License: MIT][license_badge]][license_link]
Repository to manage user authentication.
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license_link]: https://opensource.org/licenses/MIT
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis

@ -0,0 +1 @@
include: package:very_good_analysis/analysis_options.2.4.0.yaml

@ -0,0 +1,3 @@
library authentication_repository;
export 'src/authentication_repository.dart';

@ -0,0 +1,36 @@
import 'package:firebase_auth/firebase_auth.dart';
/// {@template authentication_exception}
/// Exception for authentication repository failures.
/// {@endtemplate}
class AuthenticationException implements Exception {
/// {@macro authentication_exception}
const AuthenticationException(this.error, this.stackTrace);
/// The error that was caught.
final Object error;
/// The Stacktrace associated with the [error].
final StackTrace stackTrace;
}
/// {@template authentication_repository}
/// Repository to manage user authentication.
/// {@endtemplate}
class AuthenticationRepository {
/// {@macro authentication_repository}
AuthenticationRepository(this._firebaseAuth);
final FirebaseAuth _firebaseAuth;
/// Sign in the existing user anonymously using [FirebaseAuth]. If the
/// authentication process can't be completed, it will throw an
/// [AuthenticationException].
Future<void> authenticateAnonymously() async {
try {
await _firebaseAuth.signInAnonymously();
} on Exception catch (error, stackTrace) {
throw AuthenticationException(error, stackTrace);
}
}
}

@ -0,0 +1,18 @@
name: authentication_repository
description: Repository to manage user authentication.
version: 1.0.0+1
publish_to: none
environment:
sdk: ">=2.16.0 <3.0.0"
dependencies:
firebase_auth: ^3.3.16
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
mocktail: ^0.2.0
very_good_analysis: ^2.4.0

@ -0,0 +1,40 @@
import 'package:authentication_repository/authentication_repository.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockFirebaseAuth extends Mock implements FirebaseAuth {}
class MockUserCredential extends Mock implements UserCredential {}
void main() {
late FirebaseAuth firebaseAuth;
late UserCredential userCredential;
late AuthenticationRepository authenticationRepository;
group('AuthenticationRepository', () {
setUp(() {
firebaseAuth = MockFirebaseAuth();
userCredential = MockUserCredential();
authenticationRepository = AuthenticationRepository(firebaseAuth);
});
group('authenticateAnonymously', () {
test('completes if no exception is thrown', () async {
when(() => firebaseAuth.signInAnonymously())
.thenAnswer((_) async => userCredential);
await authenticationRepository.authenticateAnonymously();
verify(() => firebaseAuth.signInAnonymously()).called(1);
});
test('throws AuthenticationException when firebase auth fails', () async {
when(() => firebaseAuth.signInAnonymously())
.thenThrow(Exception('oops'));
expect(
() => authenticationRepository.authenticateAnonymously(),
throwsA(isA<AuthenticationException>()),
);
});
});
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

@ -14,6 +14,10 @@ class $AssetsImagesGen {
$AssetsImagesBackboardGen get backboard => const $AssetsImagesBackboardGen(); $AssetsImagesBackboardGen get backboard => const $AssetsImagesBackboardGen();
$AssetsImagesBallGen get ball => const $AssetsImagesBallGen(); $AssetsImagesBallGen get ball => const $AssetsImagesBallGen();
$AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen(); $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(); $AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen();
$AssetsImagesDashGen get dash => const $AssetsImagesDashGen(); $AssetsImagesDashGen get dash => const $AssetsImagesDashGen();
$AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen(); $AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen();
@ -415,6 +419,10 @@ class $AssetsImagesSparkyComputerGen {
AssetGenImage get base => AssetGenImage get base =>
const AssetGenImage('assets/images/sparky/computer/base.png'); const AssetGenImage('assets/images/sparky/computer/base.png');
/// File path: assets/images/sparky/computer/glow.png
AssetGenImage get glow =>
const AssetGenImage('assets/images/sparky/computer/glow.png');
/// File path: assets/images/sparky/computer/top.png /// File path: assets/images/sparky/computer/top.png
AssetGenImage get top => AssetGenImage get top =>
const AssetGenImage('assets/images/sparky/computer/top.png'); const AssetGenImage('assets/images/sparky/computer/top.png');
@ -523,37 +531,37 @@ class $AssetsImagesDashBumperMainGen {
class $AssetsImagesSparkyBumperAGen { class $AssetsImagesSparkyBumperAGen {
const $AssetsImagesSparkyBumperAGen(); const $AssetsImagesSparkyBumperAGen();
/// File path: assets/images/sparky/bumper/a/active.png /// File path: assets/images/sparky/bumper/a/dimmed.png
AssetGenImage get active => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/sparky/bumper/a/active.png'); const AssetGenImage('assets/images/sparky/bumper/a/dimmed.png');
/// File path: assets/images/sparky/bumper/a/inactive.png /// File path: assets/images/sparky/bumper/a/lit.png
AssetGenImage get inactive => AssetGenImage get lit =>
const AssetGenImage('assets/images/sparky/bumper/a/inactive.png'); const AssetGenImage('assets/images/sparky/bumper/a/lit.png');
} }
class $AssetsImagesSparkyBumperBGen { class $AssetsImagesSparkyBumperBGen {
const $AssetsImagesSparkyBumperBGen(); const $AssetsImagesSparkyBumperBGen();
/// File path: assets/images/sparky/bumper/b/active.png /// File path: assets/images/sparky/bumper/b/dimmed.png
AssetGenImage get active => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/sparky/bumper/b/active.png'); const AssetGenImage('assets/images/sparky/bumper/b/dimmed.png');
/// File path: assets/images/sparky/bumper/b/inactive.png /// File path: assets/images/sparky/bumper/b/lit.png
AssetGenImage get inactive => AssetGenImage get lit =>
const AssetGenImage('assets/images/sparky/bumper/b/inactive.png'); const AssetGenImage('assets/images/sparky/bumper/b/lit.png');
} }
class $AssetsImagesSparkyBumperCGen { class $AssetsImagesSparkyBumperCGen {
const $AssetsImagesSparkyBumperCGen(); const $AssetsImagesSparkyBumperCGen();
/// File path: assets/images/sparky/bumper/c/active.png /// File path: assets/images/sparky/bumper/c/dimmed.png
AssetGenImage get active => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/sparky/bumper/c/active.png'); const AssetGenImage('assets/images/sparky/bumper/c/dimmed.png');
/// File path: assets/images/sparky/bumper/c/inactive.png /// File path: assets/images/sparky/bumper/c/lit.png
AssetGenImage get inactive => AssetGenImage get lit =>
const AssetGenImage('assets/images/sparky/bumper/c/inactive.png'); const AssetGenImage('assets/images/sparky/bumper/c/lit.png');
} }
class Assets { class Assets {

@ -115,7 +115,7 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
math.pow(defaultGravity, 2) - math.pow(positionalXForce, 2), math.pow(defaultGravity, 2) - math.pow(positionalXForce, 2),
); );
body.gravityOverride = Vector2(positionalXForce, positionalYForce); body.gravityOverride = Vector2(-positionalXForce, positionalYForce);
} }
} }

@ -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<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.boardBackground.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
}
}

@ -3,6 +3,7 @@ export 'android_spaceship.dart';
export 'backboard/backboard.dart'; export 'backboard/backboard.dart';
export 'ball.dart'; export 'ball.dart';
export 'baseboard.dart'; export 'baseboard.dart';
export 'board_background_sprite_component.dart';
export 'board_dimensions.dart'; export 'board_dimensions.dart';
export 'board_side.dart'; export 'board_side.dart';
export 'boundaries.dart'; export 'boundaries.dart';

@ -33,13 +33,13 @@ abstract class RenderPriority {
// TODO(allisonryan0002): fix this magic priority. Could bump all priorities // TODO(allisonryan0002): fix this magic priority. Could bump all priorities
// so there are no negatives. // so there are no negatives.
static const int background = 3 * _below + _base; static const int boardBackground = 3 * _below + _base;
// Boundaries // Boundaries
static const int bottomBoundary = _above + dinoBottomWall; static const int bottomBoundary = _above + dinoBottomWall;
static const int outerBoundary = _above + background; static const int outerBoundary = _above + boardBackground;
static const int outerBottomBoundary = _above + rocket; static const int outerBottomBoundary = _above + rocket;
@ -57,7 +57,7 @@ abstract class RenderPriority {
static const int rocket = _below + bottomBoundary; static const int rocket = _below + bottomBoundary;
// Dino Land // Dino Desert
static const int dinoTopWall = _above + ballOnBoard; static const int dinoTopWall = _above + ballOnBoard;
@ -71,12 +71,14 @@ abstract class RenderPriority {
static const int flutterForest = _above + launchRampForegroundRailing; static const int flutterForest = _above + launchRampForegroundRailing;
// Sparky Fire Zone // Sparky Scorch
static const int computerBase = _below + ballOnBoard; static const int computerBase = _below + ballOnBoard;
static const int computerTop = _above + ballOnBoard; static const int computerTop = _above + ballOnBoard;
static const int computerGlow = _above + ballOnBoard;
static const int sparkyAnimatronic = _above + spaceshipRampForegroundRailing; static const int sparkyAnimatronic = _above + spaceshipRampForegroundRailing;
static const int sparkyBumper = _above + ballOnBoard; static const int sparkyBumper = _above + ballOnBoard;

@ -3,19 +3,19 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
/// {@template sparky_bumper_blinking_behavior} /// {@template sparky_bumper_blinking_behavior}
/// Makes a [SparkyBumper] blink back to [SparkyBumperState.active] when /// Makes a [SparkyBumper] blink back to [SparkyBumperState.lit] when
/// [SparkyBumperState.inactive]. /// [SparkyBumperState.dimmed].
/// {@endtemplate} /// {@endtemplate}
class SparkyBumperBlinkingBehavior extends TimerComponent class SparkyBumperBlinkingBehavior extends TimerComponent
with ParentIsA<SparkyBumper> { with ParentIsA<SparkyBumper> {
/// {@macro sparky_bumper_sprite_behavior} /// {@macro sparky_bumper_blinking_behavior}
SparkyBumperBlinkingBehavior() : super(period: 0.05); SparkyBumperBlinkingBehavior() : super(period: 0.05);
void _onNewState(SparkyBumperState state) { void _onNewState(SparkyBumperState state) {
switch (state) { switch (state) {
case SparkyBumperState.active: case SparkyBumperState.lit:
break; break;
case SparkyBumperState.inactive: case SparkyBumperState.dimmed:
timer timer
..reset() ..reset()
..start(); ..start();

@ -5,13 +5,13 @@ import 'package:bloc/bloc.dart';
part 'sparky_bumper_state.dart'; part 'sparky_bumper_state.dart';
class SparkyBumperCubit extends Cubit<SparkyBumperState> { class SparkyBumperCubit extends Cubit<SparkyBumperState> {
SparkyBumperCubit() : super(SparkyBumperState.active); SparkyBumperCubit() : super(SparkyBumperState.lit);
void onBallContacted() { void onBallContacted() {
emit(SparkyBumperState.inactive); emit(SparkyBumperState.dimmed);
} }
void onBlinked() { void onBlinked() {
emit(SparkyBumperState.active); emit(SparkyBumperState.lit);
} }
} }

@ -1,10 +1,8 @@
// ignore_for_file: public_member_api_docs
part of 'sparky_bumper_cubit.dart'; part of 'sparky_bumper_cubit.dart';
/// Indicates the [SparkyBumperCubit]'s current state.
enum SparkyBumperState { enum SparkyBumperState {
/// A lit up bumper. lit,
active, dimmed,
/// A dimmed bumper.
inactive,
} }

@ -17,8 +17,8 @@ class SparkyBumper extends BodyComponent with InitialPosition {
SparkyBumper._({ SparkyBumper._({
required double majorRadius, required double majorRadius,
required double minorRadius, required double minorRadius,
required String onAssetPath, required String litAssetPath,
required String offAssetPath, required String dimmedAssetPath,
required Vector2 spritePosition, required Vector2 spritePosition,
required this.bloc, required this.bloc,
Iterable<Component>? children, Iterable<Component>? children,
@ -31,8 +31,8 @@ class SparkyBumper extends BodyComponent with InitialPosition {
SparkyBumperBallContactBehavior(), SparkyBumperBallContactBehavior(),
SparkyBumperBlinkingBehavior(), SparkyBumperBlinkingBehavior(),
_SparkyBumperSpriteGroupComponent( _SparkyBumperSpriteGroupComponent(
onAssetPath: onAssetPath, litAssetPath: litAssetPath,
offAssetPath: offAssetPath, dimmedAssetPath: dimmedAssetPath,
position: spritePosition, position: spritePosition,
state: bloc.state, state: bloc.state,
), ),
@ -46,8 +46,8 @@ class SparkyBumper extends BodyComponent with InitialPosition {
}) : this._( }) : this._(
majorRadius: 2.9, majorRadius: 2.9,
minorRadius: 2.1, minorRadius: 2.1,
onAssetPath: Assets.images.sparky.bumper.a.active.keyName, litAssetPath: Assets.images.sparky.bumper.a.lit.keyName,
offAssetPath: Assets.images.sparky.bumper.a.inactive.keyName, dimmedAssetPath: Assets.images.sparky.bumper.a.dimmed.keyName,
spritePosition: Vector2(0, -0.25), spritePosition: Vector2(0, -0.25),
bloc: SparkyBumperCubit(), bloc: SparkyBumperCubit(),
children: children, children: children,
@ -59,8 +59,8 @@ class SparkyBumper extends BodyComponent with InitialPosition {
}) : this._( }) : this._(
majorRadius: 2.85, majorRadius: 2.85,
minorRadius: 2, minorRadius: 2,
onAssetPath: Assets.images.sparky.bumper.b.active.keyName, litAssetPath: Assets.images.sparky.bumper.b.lit.keyName,
offAssetPath: Assets.images.sparky.bumper.b.inactive.keyName, dimmedAssetPath: Assets.images.sparky.bumper.b.dimmed.keyName,
spritePosition: Vector2(0, -0.35), spritePosition: Vector2(0, -0.35),
bloc: SparkyBumperCubit(), bloc: SparkyBumperCubit(),
children: children, children: children,
@ -72,8 +72,8 @@ class SparkyBumper extends BodyComponent with InitialPosition {
}) : this._( }) : this._(
majorRadius: 3, majorRadius: 3,
minorRadius: 2.2, minorRadius: 2.2,
onAssetPath: Assets.images.sparky.bumper.c.active.keyName, litAssetPath: Assets.images.sparky.bumper.c.lit.keyName,
offAssetPath: Assets.images.sparky.bumper.c.inactive.keyName, dimmedAssetPath: Assets.images.sparky.bumper.c.dimmed.keyName,
spritePosition: Vector2(0, -0.4), spritePosition: Vector2(0, -0.4),
bloc: SparkyBumperCubit(), bloc: SparkyBumperCubit(),
children: children, children: children,
@ -127,20 +127,20 @@ class _SparkyBumperSpriteGroupComponent
extends SpriteGroupComponent<SparkyBumperState> extends SpriteGroupComponent<SparkyBumperState>
with HasGameRef, ParentIsA<SparkyBumper> { with HasGameRef, ParentIsA<SparkyBumper> {
_SparkyBumperSpriteGroupComponent({ _SparkyBumperSpriteGroupComponent({
required String onAssetPath, required String litAssetPath,
required String offAssetPath, required String dimmedAssetPath,
required Vector2 position, required Vector2 position,
required SparkyBumperState state, required SparkyBumperState state,
}) : _onAssetPath = onAssetPath, }) : _litAssetPath = litAssetPath,
_offAssetPath = offAssetPath, _dimmedAssetPath = dimmedAssetPath,
super( super(
anchor: Anchor.center, anchor: Anchor.center,
position: position, position: position,
current: state, current: state,
); );
final String _onAssetPath; final String _litAssetPath;
final String _offAssetPath; final String _dimmedAssetPath;
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
@ -151,11 +151,11 @@ class _SparkyBumperSpriteGroupComponent
parent.bloc.stream.listen((state) => current = state); parent.bloc.stream.listen((state) => current = state);
final sprites = { final sprites = {
SparkyBumperState.active: Sprite( SparkyBumperState.lit: Sprite(
gameRef.images.fromCache(_onAssetPath), gameRef.images.fromCache(_litAssetPath),
), ),
SparkyBumperState.inactive: Sprite( SparkyBumperState.dimmed: Sprite(
gameRef.images.fromCache(_offAssetPath), gameRef.images.fromCache(_dimmedAssetPath),
), ),
}; };
this.sprites = sprites; this.sprites = sprites;

@ -15,6 +15,7 @@ class SparkyComputer extends Blueprint {
components: [ components: [
_ComputerBase(), _ComputerBase(),
_ComputerTopSpriteComponent(), _ComputerTopSpriteComponent(),
_ComputerGlowSpriteComponent(),
], ],
); );
} }
@ -65,15 +66,17 @@ class _ComputerBaseSpriteComponent extends SpriteComponent with HasGameRef {
_ComputerBaseSpriteComponent() _ComputerBaseSpriteComponent()
: super( : super(
anchor: Anchor.center, anchor: Anchor.center,
position: Vector2(-11.95, -48.35), position: Vector2(-12.1, -48.15),
); );
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.sparky.computer.base.keyName, Assets.images.sparky.computer.base.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
@ -84,7 +87,7 @@ class _ComputerTopSpriteComponent extends SpriteComponent with HasGameRef {
_ComputerTopSpriteComponent() _ComputerTopSpriteComponent()
: super( : super(
anchor: Anchor.center, anchor: Anchor.center,
position: Vector2(-12.45, -49.75), position: Vector2(-12.52, -49.37),
priority: RenderPriority.computerTop, priority: RenderPriority.computerTop,
); );
@ -92,8 +95,32 @@ class _ComputerTopSpriteComponent extends SpriteComponent with HasGameRef {
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.sparky.computer.top.keyName, Assets.images.sparky.computer.top.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
}
}
class _ComputerGlowSpriteComponent extends SpriteComponent with HasGameRef {
_ComputerGlowSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(7.4, 10),
priority: RenderPriority.computerGlow,
);
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.sparky.computer.glow.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;

@ -45,6 +45,7 @@ flutter:
- asset: fonts/PixeloidMono-1G8ae.ttf - asset: fonts/PixeloidMono-1G8ae.ttf
assets: assets:
- assets/images/
- assets/images/ball/ - assets/images/ball/
- assets/images/baseboard/ - assets/images/baseboard/
- assets/images/boundary/ - assets/images/boundary/

@ -19,7 +19,7 @@ void main() {
addBottomGroupStories(dashbook); addBottomGroupStories(dashbook);
addPlungerStories(dashbook); addPlungerStories(dashbook);
addSlingshotStories(dashbook); addSlingshotStories(dashbook);
addSparkyBumperStories(dashbook); addSparkyScorchStories(dashbook);
addAndroidAcresStories(dashbook); addAndroidAcresStories(dashbook);
addBoundariesStories(dashbook); addBoundariesStories(dashbook);
addGoogleWordStories(dashbook); addGoogleWordStories(dashbook);

@ -15,7 +15,7 @@ class LaunchRampGame extends BallGame {
); );
static const description = ''' static const description = '''
Shows how LaunchRamp are rendered. Shows how the LaunchRamp is rendered.
- Activate the "trace" parameter to overlay the body. - Activate the "trace" parameter to overlay the body.
- Tap anywhere on the screen to spawn a ball into the game. - Tap anywhere on the screen to spawn a ball into the game.
@ -26,7 +26,7 @@ class LaunchRampGame extends BallGame {
await super.onLoad(); await super.onLoad();
camera camera
..followVector2(Vector2(0, 0)) ..followVector2(Vector2.zero())
..zoom = 7.5; ..zoom = 7.5;
await addFromBlueprint(LaunchRamp()); await addFromBlueprint(LaunchRamp());
await ready(); await ready();

@ -1,11 +0,0 @@
import 'package:dashbook/dashbook.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/sparky_bumper/sparky_bumper_game.dart';
void addSparkyBumperStories(Dashbook dashbook) {
dashbook.storiesOf('Sparky Bumpers').addGame(
title: 'Traced',
description: SparkyBumperGame.description,
gameBuilder: (_) => SparkyBumperGame(),
);
}

@ -9,6 +9,7 @@ class SparkyBumperGame extends BallGame {
Shows how a SparkyBumper is rendered. Shows how a SparkyBumper is rendered.
- Activate the "trace" parameter to overlay the body. - Activate the "trace" parameter to overlay the body.
- Tap anywhere on the screen to spawn a ball into the game.
'''; ''';
@override @override
@ -16,12 +17,12 @@ class SparkyBumperGame extends BallGame {
await super.onLoad(); await super.onLoad();
await images.loadAll([ await images.loadAll([
Assets.images.sparky.bumper.a.active.keyName, Assets.images.sparky.bumper.a.lit.keyName,
Assets.images.sparky.bumper.a.inactive.keyName, Assets.images.sparky.bumper.a.dimmed.keyName,
Assets.images.sparky.bumper.b.active.keyName, Assets.images.sparky.bumper.b.lit.keyName,
Assets.images.sparky.bumper.b.inactive.keyName, Assets.images.sparky.bumper.b.dimmed.keyName,
Assets.images.sparky.bumper.c.active.keyName, Assets.images.sparky.bumper.c.lit.keyName,
Assets.images.sparky.bumper.c.inactive.keyName, Assets.images.sparky.bumper.c.dimmed.keyName,
]); ]);
final center = screenToWorld(camera.viewport.canvasSize! / 2); final center = screenToWorld(camera.viewport.canvasSize! / 2);

@ -0,0 +1,31 @@
import 'dart:async';
import 'package:flame/input.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class SparkyComputerGame extends BallGame {
static const description = '''
Shows how the SparkyComputer is rendered.
- Activate the "trace" parameter to overlay the body.
- Tap anywhere on the screen to spawn a ball into the game.
''';
@override
Future<void> onLoad() async {
await super.onLoad();
await images.loadAll([
Assets.images.sparky.computer.base.keyName,
Assets.images.sparky.computer.top.keyName,
Assets.images.sparky.computer.glow.keyName,
]);
camera.followVector2(Vector2(-10, -40));
await addFromBlueprint(SparkyComputer());
await ready();
await traceAllBodies();
}
}

@ -0,0 +1,18 @@
import 'package:dashbook/dashbook.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/sparky_scorch/sparky_bumper_game.dart';
import 'package:sandbox/stories/sparky_scorch/sparky_computer_game.dart';
void addSparkyScorchStories(Dashbook dashbook) {
dashbook.storiesOf('Sparky Scorch')
..addGame(
title: 'Sparky Computer',
description: SparkyComputerGame.description,
gameBuilder: (_) => SparkyComputerGame(),
)
..addGame(
title: 'Sparky Bumper',
description: SparkyBumperGame.description,
gameBuilder: (_) => SparkyBumperGame(),
);
}

@ -14,4 +14,4 @@ export 'multipliers/stories.dart';
export 'plunger/stories.dart'; export 'plunger/stories.dart';
export 'score_text/stories.dart'; export 'score_text/stories.dart';
export 'slingshot/stories.dart'; export 'slingshot/stories.dart';
export 'sparky_bumper/stories.dart'; export 'sparky_scorch/stories.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<TestGame>(),
matchesGoldenFile('golden/board-background.png'),
);
},
);
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 209 KiB

@ -31,7 +31,7 @@ void main() {
whenListen( whenListen(
bloc, bloc,
const Stream<SparkyBumperState>.empty(), const Stream<SparkyBumperState>.empty(),
initialState: SparkyBumperState.active, initialState: SparkyBumperState.lit,
); );
final sparkyBumper = SparkyBumper.test(bloc: bloc); final sparkyBumper = SparkyBumper.test(bloc: bloc);

@ -17,7 +17,7 @@ void main() {
'SparkyBumperBlinkingBehavior', 'SparkyBumperBlinkingBehavior',
() { () {
flameTester.testGameWidget( flameTester.testGameWidget(
'calls onBlinked after 0.05 seconds when inactive', 'calls onBlinked after 0.05 seconds when dimmed',
setUp: (game, tester) async { setUp: (game, tester) async {
final behavior = SparkyBumperBlinkingBehavior(); final behavior = SparkyBumperBlinkingBehavior();
final bloc = MockSparkyBumperCubit(); final bloc = MockSparkyBumperCubit();
@ -25,14 +25,14 @@ void main() {
whenListen( whenListen(
bloc, bloc,
streamController.stream, streamController.stream,
initialState: SparkyBumperState.active, initialState: SparkyBumperState.lit,
); );
final sparkyBumper = SparkyBumper.test(bloc: bloc); final sparkyBumper = SparkyBumper.test(bloc: bloc);
await sparkyBumper.add(behavior); await sparkyBumper.add(behavior);
await game.ensureAdd(sparkyBumper); await game.ensureAdd(sparkyBumper);
streamController.add(SparkyBumperState.inactive); streamController.add(SparkyBumperState.dimmed);
await tester.pump(); await tester.pump();
game.update(0.05); game.update(0.05);

@ -7,17 +7,17 @@ void main() {
'SparkyBumperCubit', 'SparkyBumperCubit',
() { () {
blocTest<SparkyBumperCubit, SparkyBumperState>( blocTest<SparkyBumperCubit, SparkyBumperState>(
'onBallContacted emits inactive', 'onBallContacted emits dimmed',
build: SparkyBumperCubit.new, build: SparkyBumperCubit.new,
act: (bloc) => bloc.onBallContacted(), act: (bloc) => bloc.onBallContacted(),
expect: () => [SparkyBumperState.inactive], expect: () => [SparkyBumperState.dimmed],
); );
blocTest<SparkyBumperCubit, SparkyBumperState>( blocTest<SparkyBumperCubit, SparkyBumperState>(
'onBlinked emits active', 'onBlinked emits lit',
build: SparkyBumperCubit.new, build: SparkyBumperCubit.new,
act: (bloc) => bloc.onBlinked(), act: (bloc) => bloc.onBlinked(),
expect: () => [SparkyBumperState.active], expect: () => [SparkyBumperState.lit],
); );
}, },
); );

@ -13,12 +13,12 @@ import '../../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [ final assets = [
Assets.images.sparky.bumper.a.active.keyName, Assets.images.sparky.bumper.a.lit.keyName,
Assets.images.sparky.bumper.a.inactive.keyName, Assets.images.sparky.bumper.a.dimmed.keyName,
Assets.images.sparky.bumper.b.active.keyName, Assets.images.sparky.bumper.b.lit.keyName,
Assets.images.sparky.bumper.b.inactive.keyName, Assets.images.sparky.bumper.b.dimmed.keyName,
Assets.images.sparky.bumper.c.active.keyName, Assets.images.sparky.bumper.c.lit.keyName,
Assets.images.sparky.bumper.c.inactive.keyName, Assets.images.sparky.bumper.c.dimmed.keyName,
]; ];
final flameTester = FlameTester(() => TestGame(assets)); final flameTester = FlameTester(() => TestGame(assets));
@ -49,7 +49,7 @@ void main() {
whenListen( whenListen(
bloc, bloc,
const Stream<SparkyBumperState>.empty(), const Stream<SparkyBumperState>.empty(),
initialState: SparkyBumperState.active, initialState: SparkyBumperState.lit,
); );
when(bloc.close).thenAnswer((_) async {}); when(bloc.close).thenAnswer((_) async {});
final sparkyBumper = SparkyBumper.test(bloc: bloc); final sparkyBumper = SparkyBumper.test(bloc: bloc);

@ -10,15 +10,33 @@ import '../../helpers/helpers.dart';
void main() { void main() {
group('SparkyComputer', () { group('SparkyComputer', () {
final tester = FlameTester(TestGame.new); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.sparky.computer.base.keyName,
Assets.images.sparky.computer.top.keyName,
Assets.images.sparky.computer.glow.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
tester.testGameWidget( flameTester.test(
'loads correctly',
(game) async {
await game.addFromBlueprint(SparkyComputer());
await game.ready();
},
);
flameTester.testGameWidget(
'renders correctly', 'renders correctly',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.addFromBlueprint(SparkyComputer()); await game.addFromBlueprint(SparkyComputer());
await game.ready(); await game.ready();
await tester.pump();
game.camera.followVector2(Vector2(-15, -50)); game.camera
..followVector2(Vector2(0, -20))
..zoom = 7;
}, },
verify: (game, tester) async { verify: (game, tester) async {
await expectLater( await expectLater(

@ -0,0 +1,16 @@
/// GENERATED CODE - DO NOT MODIFY BY HAND
/// *****************************************************
/// FlutterGen
/// *****************************************************
// ignore_for_file: directives_ordering,unnecessary_import
class FontFamily {
FontFamily._();
/// Font family: PixeloidMono
static const String pixeloidMono = 'PixeloidMono';
/// Font family: PixeloidSans
static const String pixeloidSans = 'PixeloidSans';
}

@ -1,3 +1,8 @@
library pinball_ui; 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/dialog/dialog.dart';
export 'src/external_links/external_links.dart';
export 'src/theme/theme.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<void> openLink(String url, {VoidCallback? onError}) async {
final uri = Uri.parse(url);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
} else if (onError != null) {
onError();
}
}

@ -1,17 +1,11 @@
// ignore_for_file: public_member_api_docs // ignore_for_file: public_member_api_docs
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
abstract class AppColors { abstract class PinballColors {
static const Color white = Color(0xFFFFFFFF); static const Color white = Color(0xFFFFFFFF);
static const Color darkBlue = Color(0xFF0C32A4); static const Color darkBlue = Color(0xFF0C32A4);
static const Color yellow = Color(0xFFFFEE02); static const Color yellow = Color(0xFFFFEE02);
static const Color orange = Color(0xFFE5AB05); static const Color orange = Color(0xFFE5AB05);
static const Color blue = Color(0xFF4B94F6); static const Color blue = Color(0xFF4B94F6);
static const Color transparent = Color(0x00000000); static const Color transparent = Color(0x00000000);
} }

@ -1,17 +1,18 @@
// ignore_for_file: public_member_api_docs // ignore_for_file: public_member_api_docs
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:pinball/theme/theme.dart'; import 'package:pinball_ui/gen/fonts.gen.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_ui/pinball_ui.dart';
const _fontPackage = 'pinball_components'; const _fontPackage = 'pinball_components';
const _primaryFontFamily = FontFamily.pixeloidSans; const _primaryFontFamily = FontFamily.pixeloidSans;
abstract class AppTextStyle { abstract class PinballTextStyle {
static const headline1 = TextStyle( static const headline1 = TextStyle(
fontSize: 28, fontSize: 28,
package: _fontPackage, package: _fontPackage,
fontFamily: _primaryFontFamily, fontFamily: _primaryFontFamily,
color: PinballColors.white,
); );
static const headline2 = TextStyle( static const headline2 = TextStyle(
@ -21,36 +22,22 @@ abstract class AppTextStyle {
); );
static const headline3 = TextStyle( static const headline3 = TextStyle(
color: AppColors.white, color: PinballColors.white,
fontSize: 20, fontSize: 20,
package: _fontPackage, package: _fontPackage,
fontFamily: _primaryFontFamily, fontFamily: _primaryFontFamily,
fontWeight: FontWeight.bold,
); );
static const headline4 = TextStyle( static const headline4 = TextStyle(
color: AppColors.white, color: PinballColors.white,
fontSize: 16, fontSize: 16,
package: _fontPackage, package: _fontPackage,
fontFamily: _primaryFontFamily, fontFamily: _primaryFontFamily,
); );
static const title = TextStyle(
color: AppColors.darkBlue,
fontSize: 20,
package: _fontPackage,
fontFamily: _primaryFontFamily,
);
static const subtitle3 = TextStyle(
color: AppColors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
package: _fontPackage,
fontFamily: _primaryFontFamily,
);
static const subtitle2 = TextStyle( static const subtitle2 = TextStyle(
color: AppColors.white, color: PinballColors.white,
fontSize: 16, fontSize: 16,
package: _fontPackage, package: _fontPackage,
fontFamily: _primaryFontFamily, fontFamily: _primaryFontFamily,
@ -60,5 +47,6 @@ abstract class AppTextStyle {
fontSize: 10, fontSize: 10,
fontFamily: _primaryFontFamily, fontFamily: _primaryFontFamily,
package: _fontPackage, package: _fontPackage,
color: PinballColors.yellow,
); );
} }

@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:pinball_ui/pinball_ui.dart';
/// Pinball theme
class PinballTheme {
/// Standard [ThemeData] for Pinball UI
static ThemeData get standard {
return ThemeData(
textTheme: _textTheme,
);
}
static TextTheme get _textTheme {
return const TextTheme(
headline1: PinballTextStyle.headline1,
headline2: PinballTextStyle.headline2,
headline3: PinballTextStyle.headline3,
headline4: PinballTextStyle.headline4,
subtitle1: PinballTextStyle.subtitle1,
subtitle2: PinballTextStyle.subtitle2,
);
}
}

@ -0,0 +1,3 @@
export 'pinball_colors.dart';
export 'pinball_text_style.dart';
export 'pinball_theme.dart';

@ -9,19 +9,29 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
url_launcher: ^6.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
mocktail: ^0.3.0
test: ^1.19.2 test: ^1.19.2
very_good_analysis: ^2.4.0 very_good_analysis: ^2.4.0
flutter: flutter:
uses-material-design: true uses-material-design: true
generate: true generate: true
assets: assets:
- assets/images/dialog/ - assets/images/dialog/
fonts:
- family: PixeloidSans
fonts:
- asset: fonts/PixeloidSans-nR3g1.ttf
- asset: fonts/PixeloidSansBold-RpeJo.ttf
weight: 700
- family: PixeloidMono
fonts:
- asset: fonts/PixeloidMono-1G8ae.ttf
flutter_gen: flutter_gen:
line_length: 80 line_length: 80

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

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_ui/pinball_ui.dart';
void main() {
group('PinballColors', () {
test('white is 0xFFFFFFFF', () {
expect(PinballColors.white, const Color(0xFFFFFFFF));
});
test('darkBlue is 0xFF0C32A4', () {
expect(PinballColors.darkBlue, const Color(0xFF0C32A4));
});
test('yellow is 0xFFFFEE02', () {
expect(PinballColors.yellow, const Color(0xFFFFEE02));
});
test('orange is 0xFFE5AB05', () {
expect(PinballColors.orange, const Color(0xFFE5AB05));
});
test('blue is 0xFF4B94F6', () {
expect(PinballColors.blue, const Color(0xFF4B94F6));
});
test('transparent is 0x00000000', () {
expect(PinballColors.transparent, const Color(0x00000000));
});
});
}

@ -0,0 +1,41 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_ui/pinball_ui.dart';
void main() {
group('PinballTextStyle', () {
test('headline1 has fontSize 28 and white color', () {
const style = PinballTextStyle.headline1;
expect(style.fontSize, 28);
expect(style.color, PinballColors.white);
});
test('headline2 has fontSize 24', () {
const style = PinballTextStyle.headline2;
expect(style.fontSize, 24);
});
test('headline3 has fontSize 20 and white color', () {
const style = PinballTextStyle.headline3;
expect(style.fontSize, 20);
expect(style.color, PinballColors.white);
});
test('headline4 has fontSize 16 and white color', () {
const style = PinballTextStyle.headline4;
expect(style.fontSize, 16);
expect(style.color, PinballColors.white);
});
test('subtitle1 has fontSize 10 and yellow color', () {
const style = PinballTextStyle.subtitle1;
expect(style.fontSize, 10);
expect(style.color, PinballColors.yellow);
});
test('subtitle2 has fontSize 16 and white color', () {
const style = PinballTextStyle.subtitle2;
expect(style.fontSize, 16);
expect(style.color, PinballColors.white);
});
});
}

@ -0,0 +1,98 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_ui/pinball_ui.dart';
void main() {
group('PinballTheme', () {
group('standard', () {
test('headline1 matches PinballTextStyle#headline1', () {
expect(
PinballTheme.standard.textTheme.headline1!.fontSize,
PinballTextStyle.headline1.fontSize,
);
expect(
PinballTheme.standard.textTheme.headline1!.color,
PinballTextStyle.headline1.color,
);
expect(
PinballTheme.standard.textTheme.headline1!.fontFamily,
PinballTextStyle.headline1.fontFamily,
);
});
test('headline2 matches PinballTextStyle#headline2', () {
expect(
PinballTheme.standard.textTheme.headline2!.fontSize,
PinballTextStyle.headline2.fontSize,
);
expect(
PinballTheme.standard.textTheme.headline2!.fontFamily,
PinballTextStyle.headline2.fontFamily,
);
expect(
PinballTheme.standard.textTheme.headline2!.fontWeight,
PinballTextStyle.headline2.fontWeight,
);
});
test('headline3 matches PinballTextStyle#headline3', () {
expect(
PinballTheme.standard.textTheme.headline3!.fontSize,
PinballTextStyle.headline3.fontSize,
);
expect(
PinballTheme.standard.textTheme.headline3!.color,
PinballTextStyle.headline3.color,
);
expect(
PinballTheme.standard.textTheme.headline3!.fontFamily,
PinballTextStyle.headline3.fontFamily,
);
});
test('headline4 matches PinballTextStyle#headline4', () {
expect(
PinballTheme.standard.textTheme.headline4!.fontSize,
PinballTextStyle.headline4.fontSize,
);
expect(
PinballTheme.standard.textTheme.headline4!.color,
PinballTextStyle.headline4.color,
);
expect(
PinballTheme.standard.textTheme.headline4!.fontFamily,
PinballTextStyle.headline4.fontFamily,
);
});
test('subtitle1 matches PinballTextStyle#subtitle1', () {
expect(
PinballTheme.standard.textTheme.subtitle1!.fontSize,
PinballTextStyle.subtitle1.fontSize,
);
expect(
PinballTheme.standard.textTheme.subtitle1!.color,
PinballTextStyle.subtitle1.color,
);
expect(
PinballTheme.standard.textTheme.subtitle1!.fontFamily,
PinballTextStyle.subtitle1.fontFamily,
);
});
test('subtitle2 matches PinballTextStyle#subtitle2', () {
expect(
PinballTheme.standard.textTheme.subtitle2!.fontSize,
PinballTextStyle.subtitle2.fontSize,
);
expect(
PinballTheme.standard.textTheme.subtitle2!.color,
PinballTextStyle.subtitle2.color,
);
expect(
PinballTheme.standard.textTheme.subtitle2!.fontFamily,
PinballTextStyle.subtitle2.fontFamily,
);
});
});
});
}

@ -36,6 +36,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.20.1" version: "0.20.1"
authentication_repository:
dependency: "direct main"
description:
path: "packages/authentication_repository"
relative: true
source: path
version: "1.0.0+1"
bloc: bloc:
dependency: "direct main" dependency: "direct main"
description: description:
@ -169,13 +176,34 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.2" version: "6.1.2"
firebase_core: firebase_auth:
dependency: "direct main"
description:
name: firebase_auth
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.16"
firebase_auth_platform_interface:
dependency: transitive dependency: transitive
description:
name: firebase_auth_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "6.2.4"
firebase_auth_web:
dependency: transitive
description:
name: firebase_auth_web
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.13"
firebase_core:
dependency: "direct main"
description: description:
name: firebase_core name: firebase_core
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.13.1" version: "1.15.0"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -189,7 +217,7 @@ packages:
name: firebase_core_web name: firebase_core_web
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.6.1" version: "1.6.2"
flame: flame:
dependency: "direct main" dependency: "direct main"
description: description:
@ -672,6 +700,62 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" 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: uuid:
dependency: transitive dependency: transitive
description: description:
@ -744,4 +828,4 @@ packages:
version: "3.1.0" version: "3.1.0"
sdks: sdks:
dart: ">=2.16.0 <3.0.0" dart: ">=2.16.0 <3.0.0"
flutter: ">=2.8.0" flutter: ">=2.10.0"

@ -7,9 +7,13 @@ environment:
sdk: ">=2.16.0 <3.0.0" sdk: ">=2.16.0 <3.0.0"
dependencies: dependencies:
authentication_repository:
path: packages/authentication_repository
bloc: ^8.0.2 bloc: ^8.0.2
cloud_firestore: ^3.1.10 cloud_firestore: ^3.1.10
equatable: ^2.0.3 equatable: ^2.0.3
firebase_auth: ^3.3.16
firebase_core: ^1.15.0
flame: ^1.1.1 flame: ^1.1.1
flame_bloc: ^1.2.0 flame_bloc: ^1.2.0
flame_forge2d: flame_forge2d:

@ -5,6 +5,7 @@
// license that can be found in the LICENSE file or at // license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT. // https://opensource.org/licenses/MIT.
import 'package:authentication_repository/authentication_repository.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@ -16,19 +17,21 @@ import '../../helpers/mocks.dart';
void main() { void main() {
group('App', () { group('App', () {
late AuthenticationRepository authenticationRepository;
late LeaderboardRepository leaderboardRepository; late LeaderboardRepository leaderboardRepository;
late PinballAudio pinballAudio; late PinballAudio pinballAudio;
setUp(() { setUp(() {
authenticationRepository = MockAuthenticationRepository();
leaderboardRepository = MockLeaderboardRepository(); leaderboardRepository = MockLeaderboardRepository();
pinballAudio = MockPinballAudio(); pinballAudio = MockPinballAudio();
when(pinballAudio.load).thenAnswer((_) => Future.value()); when(pinballAudio.load).thenAnswer((_) => Future.value());
}); });
testWidgets('renders PinballGamePage', (tester) async { testWidgets('renders PinballGamePage', (tester) async {
await tester.pumpWidget( await tester.pumpWidget(
App( App(
authenticationRepository: authenticationRepository,
leaderboardRepository: leaderboardRepository, leaderboardRepository: leaderboardRepository,
pinballAudio: pinballAudio, pinballAudio: pinballAudio,
), ),

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

@ -12,22 +12,25 @@ import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [ final assets = [
Assets.images.sparky.bumper.a.active.keyName, Assets.images.sparky.computer.top.keyName,
Assets.images.sparky.bumper.a.inactive.keyName, Assets.images.sparky.computer.base.keyName,
Assets.images.sparky.bumper.b.active.keyName, Assets.images.sparky.computer.glow.keyName,
Assets.images.sparky.bumper.b.inactive.keyName,
Assets.images.sparky.bumper.c.active.keyName,
Assets.images.sparky.bumper.c.inactive.keyName,
Assets.images.sparky.animatronic.keyName, Assets.images.sparky.animatronic.keyName,
Assets.images.sparky.bumper.a.lit.keyName,
Assets.images.sparky.bumper.a.dimmed.keyName,
Assets.images.sparky.bumper.b.lit.keyName,
Assets.images.sparky.bumper.b.dimmed.keyName,
Assets.images.sparky.bumper.c.lit.keyName,
Assets.images.sparky.bumper.c.dimmed.keyName,
]; ];
final flameTester = FlameTester( final flameTester = FlameTester(
() => EmptyPinballTestGame(assets: assets), () => EmptyPinballTestGame(assets: assets),
); );
group('SparkyFireZone', () { group('SparkyScorch', () {
flameTester.test('loads correctly', (game) async { flameTester.test('loads correctly', (game) async {
await game.addFromBlueprint(SparkyFireZone()); await game.addFromBlueprint(SparkyScorch());
await game.ready(); await game.ready();
}); });
@ -36,7 +39,7 @@ void main() {
'a SparkyComputer', 'a SparkyComputer',
(game) async { (game) async {
expect( expect(
SparkyFireZone().blueprints.whereType<SparkyComputer>().single, SparkyScorch().blueprints.whereType<SparkyComputer>().single,
isNotNull, isNotNull,
); );
}, },
@ -45,8 +48,8 @@ void main() {
flameTester.test( flameTester.test(
'a SparkyAnimatronic', 'a SparkyAnimatronic',
(game) async { (game) async {
final sparkyFireZone = SparkyFireZone(); final sparkysScorch = SparkyScorch();
await game.addFromBlueprint(sparkyFireZone); await game.addFromBlueprint(sparkysScorch);
await game.ready(); await game.ready();
expect( expect(
@ -59,8 +62,8 @@ void main() {
flameTester.test( flameTester.test(
'three SparkyBumper', 'three SparkyBumper',
(game) async { (game) async {
final sparkyFireZone = SparkyFireZone(); final sparkysScorch = SparkyScorch();
await game.addFromBlueprint(sparkyFireZone); await game.addFromBlueprint(sparkysScorch);
await game.ready(); await game.ready();
expect( expect(

@ -24,6 +24,7 @@ void main() {
Assets.images.backboard.backboardScores.keyName, Assets.images.backboard.backboardScores.keyName,
Assets.images.backboard.backboardGameOver.keyName, Assets.images.backboard.backboardGameOver.keyName,
Assets.images.backboard.display.keyName, Assets.images.backboard.display.keyName,
Assets.images.boardBackground.keyName,
Assets.images.ball.ball.keyName, Assets.images.ball.ball.keyName,
Assets.images.ball.flameEffect.keyName, Assets.images.ball.flameEffect.keyName,
Assets.images.baseboard.left.keyName, Assets.images.baseboard.left.keyName,
@ -88,22 +89,17 @@ void main() {
Assets.images.android.ramp.arrow.active5.keyName, Assets.images.android.ramp.arrow.active5.keyName,
Assets.images.android.rail.main.keyName, Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName, Assets.images.android.rail.exit.keyName,
Assets.images.sparky.bumper.a.active.keyName,
Assets.images.sparky.bumper.a.inactive.keyName,
Assets.images.sparky.bumper.b.active.keyName,
Assets.images.sparky.bumper.b.inactive.keyName,
Assets.images.sparky.bumper.c.active.keyName,
Assets.images.sparky.bumper.c.inactive.keyName,
Assets.images.sparky.animatronic.keyName, Assets.images.sparky.animatronic.keyName,
Assets.images.sparky.computer.top.keyName, Assets.images.sparky.computer.top.keyName,
Assets.images.sparky.computer.base.keyName, Assets.images.sparky.computer.base.keyName,
Assets.images.sparky.computer.glow.keyName,
Assets.images.sparky.animatronic.keyName, Assets.images.sparky.animatronic.keyName,
Assets.images.sparky.bumper.a.inactive.keyName, Assets.images.sparky.bumper.a.lit.keyName,
Assets.images.sparky.bumper.a.active.keyName, Assets.images.sparky.bumper.a.dimmed.keyName,
Assets.images.sparky.bumper.b.active.keyName, Assets.images.sparky.bumper.b.lit.keyName,
Assets.images.sparky.bumper.b.inactive.keyName, Assets.images.sparky.bumper.b.dimmed.keyName,
Assets.images.sparky.bumper.c.active.keyName, Assets.images.sparky.bumper.c.lit.keyName,
Assets.images.sparky.bumper.c.inactive.keyName, Assets.images.sparky.bumper.c.dimmed.keyName,
]; ];
late GameBloc gameBloc; late GameBloc gameBloc;

@ -15,6 +15,7 @@ import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets; import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_ui/pinball_ui.dart';
import '../../../helpers/helpers.dart'; import '../../../helpers/helpers.dart';
@ -56,6 +57,7 @@ void main() {
Future<void> _pumpAppWithWidget(WidgetTester tester) async { Future<void> _pumpAppWithWidget(WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: PinballTheme.standard,
localizationsDelegates: const [ localizationsDelegates: const [
AppLocalizations.delegate, AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,

@ -2,7 +2,7 @@ import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/theme/app_colors.dart'; import 'package:pinball_ui/pinball_ui.dart';
import '../../../helpers/helpers.dart'; import '../../../helpers/helpers.dart';
@ -108,7 +108,7 @@ void main() {
expect( expect(
find.byWidgetPredicate( find.byWidgetPredicate(
(widget) => widget is Container && widget.color == AppColors.yellow, (widget) => widget is Container && widget.color == PinballColors.yellow,
), ),
findsOneWidget, findsOneWidget,
); );
@ -125,7 +125,7 @@ void main() {
find.byWidgetPredicate( find.byWidgetPredicate(
(widget) => (widget) =>
widget is Container && widget is Container &&
widget.color == AppColors.yellow.withAlpha(128), widget.color == PinballColors.yellow.withAlpha(128),
), ),
findsOneWidget, findsOneWidget,
); );

@ -12,3 +12,4 @@ export 'mocks.dart';
export 'navigator.dart'; export 'navigator.dart';
export 'pump_app.dart'; export 'pump_app.dart';
export 'test_games.dart'; export 'test_games.dart';
export 'text_span.dart';

@ -1,3 +1,4 @@
import 'package:authentication_repository/authentication_repository.dart';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
@ -11,6 +12,8 @@ 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_components/pinball_components.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 {} class MockPinballGame extends Mock implements PinballGame {}
@ -34,6 +37,9 @@ class MockGameState extends Mock implements GameState {}
class MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {} class MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {}
class MockAuthenticationRepository extends Mock
implements AuthenticationRepository {}
class MockLeaderboardRepository extends Mock implements LeaderboardRepository {} class MockLeaderboardRepository extends Mock implements LeaderboardRepository {}
class MockRawKeyDownEvent extends Mock implements RawKeyDownEvent { class MockRawKeyDownEvent extends Mock implements RawKeyDownEvent {
@ -94,3 +100,7 @@ class MockMultiplier extends Mock implements Multiplier {}
class MockMultipliersGroup extends Mock implements Multipliers {} class MockMultipliersGroup extends Mock implements Multipliers {}
class MockMultiplierCubit extends Mock implements MultiplierCubit {} class MockMultiplierCubit extends Mock implements MultiplierCubit {}
class MockUrlLauncher extends Mock
with MockPlatformInterfaceMixin
implements UrlLauncherPlatform {}

@ -17,6 +17,7 @@ import 'package:pinball/l10n/l10n.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 'helpers.dart'; import 'helpers.dart';
@ -85,6 +86,7 @@ extension PumpApp on WidgetTester {
), ),
], ],
child: MaterialApp( child: MaterialApp(
theme: PinballTheme.standard,
localizationsDelegates: const [ localizationsDelegates: const [
AppLocalizations.delegate, AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,11 +1,11 @@
if (typeof firebase === 'undefined') throw new Error('hosting/init-error: Firebase SDK not detected. You must include it before /__/firebase/init.js'); if (typeof firebase === 'undefined') throw new Error('hosting/init-error: Firebase SDK not detected. You must include it before /__/firebase/init.js');
firebase.initializeApp({ firebase.initializeApp({
"apiKey": "API_KEY", "apiKey": "AIzaSyBgMVAvYccjNypCDdpW0ol6syCcISU2yjM",
"appId": "APP_ID", "appId": "1:725488140557:web:7c61a0755fc23436fe7044",
"authDomain": "AUTH_DOMAIN", "authDomain": "pinball-dev.firebaseapp.com",
"databaseURL": "", "databaseURL": "",
"measurementId": "MEASUREMENT_ID", "measurementId": "G-9NW8SZRFJR",
"messagingSenderId": "MEASUREMENT_SENDER_ID", "messagingSenderId": "725488140557",
"projectId": "PROJECT_ID", "projectId": "pinball-dev",
"storageBucket": "STORAGE_BUCKET" "storageBucket": "pinball-dev.appspot.com"
}); });

@ -56,15 +56,24 @@
<title>I/O Pinball Machine - Flutter</title> <title>I/O Pinball Machine - Flutter</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-67589403-1"></script>
<script> <script>
window.dataLayer = window.dataLayer || []; (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
function gtag(){dataLayer.push(arguments);} new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
gtag('js', new Date()); j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
gtag('config', 'UA-67589403-1'); 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-ND4LWWZ');
</script> </script>
<script src="/__/firebase/8.9.1/firebase-app.js"></script> <script>
<script src="/__/firebase/8.9.1/firebase-firestore.js"></script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-67589403-1', 'auto');
ga('send', 'pageview');
</script>
<script src="/__/firebase/8.10.1/firebase-app.js"></script>
<script src="/__/firebase/8.10.1/firebase-firestore.js"></script>
<script src="/__/firebase/8.10.1/firebase-auth.js"></script>
<script src="/__/firebase/init.js"></script> <script src="/__/firebase/init.js"></script>
</head> </head>

Loading…
Cancel
Save