diff --git a/.github/workflows/pinball_audio.yaml b/.github/workflows/pinball_audio.yaml new file mode 100644 index 00000000..7a43413a --- /dev/null +++ b/.github/workflows/pinball_audio.yaml @@ -0,0 +1,19 @@ +name: pinball_audio + +on: + push: + paths: + - "packages/pinball_audio/**" + - ".github/workflows/pinball_audio.yaml" + + pull_request: + paths: + - "packages/pinball_audio/**" + - ".github/workflows/pinball_audio.yaml" + +jobs: + build: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 + with: + working_directory: packages/pinball_audio + coverage_excludes: "lib/gen/*.dart" diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 8de80730..521d575e 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -13,18 +13,27 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/landing/landing.dart'; +import 'package:pinball_audio/pinball_audio.dart'; class App extends StatelessWidget { - const App({Key? key, required LeaderboardRepository leaderboardRepository}) - : _leaderboardRepository = leaderboardRepository, + const App({ + Key? key, + required LeaderboardRepository leaderboardRepository, + required PinballAudio pinballAudio, + }) : _leaderboardRepository = leaderboardRepository, + _pinballAudio = pinballAudio, super(key: key); final LeaderboardRepository _leaderboardRepository; + final PinballAudio _pinballAudio; @override Widget build(BuildContext context) { - return RepositoryProvider.value( - value: _leaderboardRepository, + return MultiRepositoryProvider( + providers: [ + RepositoryProvider.value(value: _leaderboardRepository), + RepositoryProvider.value(value: _pinballAudio), + ], child: MaterialApp( title: 'I/O Pinball', theme: ThemeData( diff --git a/lib/game/components/bonus_word.dart b/lib/game/components/bonus_word.dart index e7f1626a..4aab67a9 100644 --- a/lib/game/components/bonus_word.dart +++ b/lib/game/components/bonus_word.dart @@ -8,12 +8,14 @@ import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template bonus_word} /// Loads all [BonusLetter]s to compose a [BonusWord]. /// {@endtemplate} -class BonusWord extends Component with BlocComponent { +class BonusWord extends Component + with BlocComponent, HasGameRef { /// {@macro bonus_word} BonusWord({required Vector2 position}) : _position = position; @@ -29,6 +31,8 @@ class BonusWord extends Component with BlocComponent { @override void onNewState(GameState state) { if (state.bonusHistory.last == GameBonus.word) { + gameRef.read().googleBonus(); + final letters = children.whereType().toList(); for (var i = 0; i < letters.length; i++) { diff --git a/lib/game/components/score_points.dart b/lib/game/components/score_points.dart index 39910777..b71d1f6e 100644 --- a/lib/game/components/score_points.dart +++ b/lib/game/components/score_points.dart @@ -2,6 +2,7 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template score_points} @@ -37,5 +38,7 @@ class BallScorePointsCallback extends ContactCallback { _gameRef.read().add( Scored(points: scorePoints.points), ); + + _gameRef.read().score(); } } diff --git a/lib/game/view/pinball_game_page.dart b/lib/game/view/pinball_game_page.dart index 0fa6a1ad..0f58a20d 100644 --- a/lib/game/view/pinball_game_page.dart +++ b/lib/game/view/pinball_game_page.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_theme/pinball_theme.dart'; class PinballGamePage extends StatelessWidget { @@ -46,6 +47,7 @@ class PinballGameView extends StatefulWidget { class _PinballGameViewState extends State { late PinballGame _game; + bool _loading = true; @override void initState() { @@ -54,14 +56,31 @@ class _PinballGameViewState extends State { // TODO(erickzanardo): Revisit this when we start to have more assets // this could expose a Stream (maybe even a cubit?) so we could show the // the loading progress with some fancy widgets. - _game = (widget._isDebugMode + _game = widget._isDebugMode ? DebugPinballGame(theme: widget.theme) - : PinballGame(theme: widget.theme)) - ..preLoadAssets(); + : PinballGame(theme: widget.theme); + + _fetchAssets(); + } + + Future _fetchAssets() async { + final pinballAudio = context.read(); + await Future.wait([ + _game.preLoadAssets(), + pinballAudio.load(), + ]); + + setState(() { + _loading = false; + }); } @override Widget build(BuildContext context) { + if (_loading) { + return const Center(child: CircularProgressIndicator()); + } + return BlocListener( listenWhen: (previous, current) => previous.isGameOver != current.isGameOver, diff --git a/lib/main_development.dart b/lib/main_development.dart index 8673eff4..8944073d 100644 --- a/lib/main_development.dart +++ b/lib/main_development.dart @@ -8,10 +8,15 @@ import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/app/app.dart'; import 'package:pinball/bootstrap.dart'; +import 'package:pinball_audio/pinball_audio.dart'; void main() { bootstrap((firestore) async { final leaderboardRepository = LeaderboardRepository(firestore); - return App(leaderboardRepository: leaderboardRepository); + final pinballAudio = PinballAudio(); + return App( + leaderboardRepository: leaderboardRepository, + pinballAudio: pinballAudio, + ); }); } diff --git a/lib/main_production.dart b/lib/main_production.dart index 8673eff4..8944073d 100644 --- a/lib/main_production.dart +++ b/lib/main_production.dart @@ -8,10 +8,15 @@ import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/app/app.dart'; import 'package:pinball/bootstrap.dart'; +import 'package:pinball_audio/pinball_audio.dart'; void main() { bootstrap((firestore) async { final leaderboardRepository = LeaderboardRepository(firestore); - return App(leaderboardRepository: leaderboardRepository); + final pinballAudio = PinballAudio(); + return App( + leaderboardRepository: leaderboardRepository, + pinballAudio: pinballAudio, + ); }); } diff --git a/lib/main_staging.dart b/lib/main_staging.dart index 8673eff4..8944073d 100644 --- a/lib/main_staging.dart +++ b/lib/main_staging.dart @@ -8,10 +8,15 @@ import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/app/app.dart'; import 'package:pinball/bootstrap.dart'; +import 'package:pinball_audio/pinball_audio.dart'; void main() { bootstrap((firestore) async { final leaderboardRepository = LeaderboardRepository(firestore); - return App(leaderboardRepository: leaderboardRepository); + final pinballAudio = PinballAudio(); + return App( + leaderboardRepository: leaderboardRepository, + pinballAudio: pinballAudio, + ); }); } diff --git a/packages/pinball_audio/.gitignore b/packages/pinball_audio/.gitignore new file mode 100644 index 00000000..d6130351 --- /dev/null +++ b/packages/pinball_audio/.gitignore @@ -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 diff --git a/packages/pinball_audio/README.md b/packages/pinball_audio/README.md new file mode 100644 index 00000000..f8b69df7 --- /dev/null +++ b/packages/pinball_audio/README.md @@ -0,0 +1,11 @@ +# pinball_audio + +[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] +[![License: MIT][license_badge]][license_link] + +Package with the sound manager for the pinball game + +[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 \ No newline at end of file diff --git a/packages/pinball_audio/analysis_options.yaml b/packages/pinball_audio/analysis_options.yaml new file mode 100644 index 00000000..f8155aa6 --- /dev/null +++ b/packages/pinball_audio/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:very_good_analysis/analysis_options.2.4.0.yaml +analyzer: + exclude: + - lib/**/*.gen.dart diff --git a/packages/pinball_audio/assets/sfx/google.ogg b/packages/pinball_audio/assets/sfx/google.ogg new file mode 100644 index 00000000..dafaa8d4 Binary files /dev/null and b/packages/pinball_audio/assets/sfx/google.ogg differ diff --git a/packages/pinball_audio/assets/sfx/plim.ogg b/packages/pinball_audio/assets/sfx/plim.ogg new file mode 100644 index 00000000..137c22b7 Binary files /dev/null and b/packages/pinball_audio/assets/sfx/plim.ogg differ diff --git a/packages/pinball_audio/lib/gen/assets.gen.dart b/packages/pinball_audio/lib/gen/assets.gen.dart new file mode 100644 index 00000000..3609b939 --- /dev/null +++ b/packages/pinball_audio/lib/gen/assets.gen.dart @@ -0,0 +1,69 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +import 'package:flutter/widgets.dart'; + +class $AssetsSfxGen { + const $AssetsSfxGen(); + + String get google => 'assets/sfx/google.ogg'; + String get plim => 'assets/sfx/plim.ogg'; +} + +class Assets { + Assets._(); + + static const $AssetsSfxGen sfx = $AssetsSfxGen(); +} + +class AssetGenImage extends AssetImage { + const AssetGenImage(String assetName) + : super(assetName, package: 'pinball_audio'); + + Image image({ + Key? key, + ImageFrameBuilder? frameBuilder, + ImageLoadingBuilder? loadingBuilder, + ImageErrorWidgetBuilder? errorBuilder, + String? semanticLabel, + bool excludeFromSemantics = false, + double? width, + double? height, + Color? color, + BlendMode? colorBlendMode, + BoxFit? fit, + AlignmentGeometry alignment = Alignment.center, + ImageRepeat repeat = ImageRepeat.noRepeat, + Rect? centerSlice, + bool matchTextDirection = false, + bool gaplessPlayback = false, + bool isAntiAlias = false, + FilterQuality filterQuality = FilterQuality.low, + }) { + return Image( + key: key, + image: this, + frameBuilder: frameBuilder, + loadingBuilder: loadingBuilder, + errorBuilder: errorBuilder, + semanticLabel: semanticLabel, + excludeFromSemantics: excludeFromSemantics, + width: width, + height: height, + color: color, + colorBlendMode: colorBlendMode, + fit: fit, + alignment: alignment, + repeat: repeat, + centerSlice: centerSlice, + matchTextDirection: matchTextDirection, + gaplessPlayback: gaplessPlayback, + isAntiAlias: isAntiAlias, + filterQuality: filterQuality, + ); + } + + String get path => assetName; +} diff --git a/packages/pinball_audio/lib/pinball_audio.dart b/packages/pinball_audio/lib/pinball_audio.dart new file mode 100644 index 00000000..a72ce165 --- /dev/null +++ b/packages/pinball_audio/lib/pinball_audio.dart @@ -0,0 +1,3 @@ +library pinball_audio; + +export 'src/pinball_audio.dart'; diff --git a/packages/pinball_audio/lib/src/pinball_audio.dart b/packages/pinball_audio/lib/src/pinball_audio.dart new file mode 100644 index 00000000..65fe8a68 --- /dev/null +++ b/packages/pinball_audio/lib/src/pinball_audio.dart @@ -0,0 +1,37 @@ +import 'package:flame_audio/audio_pool.dart'; +import 'package:flame_audio/flame_audio.dart'; +import 'package:pinball_audio/gen/assets.gen.dart'; + +/// {@template pinball_audio} +/// Sound manager for the pinball game +/// {@endtemplate} +class PinballAudio { + /// {@macro pinball_audio} + PinballAudio(); + + late AudioPool _scorePool; + + /// Loads the sounds effects into the memory + Future load() async { + FlameAudio.audioCache.prefix = ''; + _scorePool = await AudioPool.create( + _prefixFile(Assets.sfx.plim), + maxPlayers: 4, + prefix: '', + ); + } + + /// Plays the basic score sound + void score() { + _scorePool.start(); + } + + /// Plays the google word bonus + void googleBonus() { + FlameAudio.audioCache.play(_prefixFile(Assets.sfx.google)); + } + + String _prefixFile(String file) { + return 'packages/pinball_audio/$file'; + } +} diff --git a/packages/pinball_audio/pubspec.yaml b/packages/pinball_audio/pubspec.yaml new file mode 100644 index 00000000..ab59222e --- /dev/null +++ b/packages/pinball_audio/pubspec.yaml @@ -0,0 +1,26 @@ +name: pinball_audio +description: Package with the sound manager for the pinball game +version: 1.0.0+1 +publish_to: none + +environment: + sdk: ">=2.16.0 <3.0.0" + +dependencies: + flame_audio: ^1.0.1 + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + very_good_analysis: ^2.4.0 + +flutter_gen: + line_length: 80 + assets: + package_parameter_enabled: true + +flutter: + assets: + - assets/sfx/ diff --git a/packages/pinball_audio/test/src/pinball_audio_test.dart b/packages/pinball_audio/test/src/pinball_audio_test.dart new file mode 100644 index 00000000..a047ebc7 --- /dev/null +++ b/packages/pinball_audio/test/src/pinball_audio_test.dart @@ -0,0 +1,11 @@ +// ignore_for_file: prefer_const_constructors +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_audio/pinball_audio.dart'; + +void main() { + group('PinballAudio', () { + test('can be instantiated', () { + expect(PinballAudio(), isNotNull); + }); + }); +} diff --git a/pubspec.lock b/pubspec.lock index ada9db4e..240c5a9f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,6 +29,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.8.2" + audioplayers: + dependency: transitive + description: + name: audioplayers + url: "https://pub.dartlang.org" + source: hosted + version: "0.20.1" bloc: dependency: "direct main" description: @@ -148,6 +155,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" file: dependency: transitive description: @@ -183,6 +197,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + flame_audio: + dependency: transitive + description: + name: flame_audio + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" flame_bloc: dependency: "direct main" description: @@ -259,6 +280,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.2" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.4" http_multi_server: dependency: transitive description: @@ -392,6 +420,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" + path_provider: + dependency: transitive + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.12" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + pinball_audio: + dependency: "direct main" + description: + path: "packages/pinball_audio" + relative: true + source: path + version: "1.0.0+1" pinball_components: dependency: "direct main" description: @@ -406,6 +490,13 @@ packages: relative: true source: path version: "1.0.0+1" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" plugin_platform_interface: dependency: transitive description: @@ -420,6 +511,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.0" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" provider: dependency: transitive description: @@ -509,6 +607,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0+2" term_glyph: dependency: transitive description: @@ -544,6 +649,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.6" vector_math: dependency: transitive description: @@ -586,6 +698,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+1" yaml: dependency: transitive description: @@ -595,4 +721,4 @@ packages: version: "3.1.0" sdks: dart: ">=2.16.0 <3.0.0" - flutter: ">=2.5.0" + flutter: ">=2.8.0" diff --git a/pubspec.yaml b/pubspec.yaml index a0cca553..161afb85 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,8 @@ dependencies: intl: ^0.17.0 leaderboard_repository: path: packages/leaderboard_repository + pinball_audio: + path: packages/pinball_audio pinball_components: path: packages/pinball_components pinball_theme: