diff --git a/.gitignore b/.gitignore index eeb2b0f6..9bf37325 100644 --- a/.gitignore +++ b/.gitignore @@ -126,4 +126,6 @@ app.*.map.json !.idea/dictionaries/ !.idea/runConfigurations/ +# Firebase related .firebase +web/__/firebase/init.js diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index cf6213e9..8de80730 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -8,29 +8,38 @@ // ignore_for_file: public_member_api_docs import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; 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'; class App extends StatelessWidget { - const App({Key? key}) : super(key: key); + const App({Key? key, required LeaderboardRepository leaderboardRepository}) + : _leaderboardRepository = leaderboardRepository, + super(key: key); + + final LeaderboardRepository _leaderboardRepository; @override Widget build(BuildContext context) { - return MaterialApp( - title: 'I/O Pinball', - theme: ThemeData( - appBarTheme: const AppBarTheme(color: Color(0xFF13B9FF)), - colorScheme: ColorScheme.fromSwatch( - accentColor: const Color(0xFF13B9FF), + return RepositoryProvider.value( + value: _leaderboardRepository, + child: MaterialApp( + title: 'I/O Pinball', + theme: ThemeData( + appBarTheme: const AppBarTheme(color: Color(0xFF13B9FF)), + colorScheme: ColorScheme.fromSwatch( + accentColor: const Color(0xFF13B9FF), + ), ), + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, + home: const LandingPage(), ), - localizationsDelegates: const [ - AppLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - ], - supportedLocales: AppLocalizations.supportedLocales, - home: const LandingPage(), ); } } diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index 34fcc47a..c0fa9240 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -11,6 +11,7 @@ import 'dart:async'; import 'dart:developer'; import 'package:bloc/bloc.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/widgets.dart'; class AppBlocObserver extends BlocObserver { @@ -27,7 +28,10 @@ class AppBlocObserver extends BlocObserver { } } -Future bootstrap(FutureOr Function() builder) async { +Future bootstrap( + Future Function(FirebaseFirestore firestore) builder, +) async { + WidgetsFlutterBinding.ensureInitialized(); FlutterError.onError = (details) { log(details.exceptionAsString(), stackTrace: details.stack); }; @@ -35,7 +39,7 @@ Future bootstrap(FutureOr Function() builder) async { await runZonedGuarded( () async { await BlocOverrides.runZoned( - () async => runApp(await builder()), + () async => runApp(await builder(FirebaseFirestore.instance)), blocObserver: AppBlocObserver(), ); }, diff --git a/lib/main_development.dart b/lib/main_development.dart index 99927436..8673eff4 100644 --- a/lib/main_development.dart +++ b/lib/main_development.dart @@ -5,9 +5,13 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/app/app.dart'; import 'package:pinball/bootstrap.dart'; void main() { - bootstrap(() => const App()); + bootstrap((firestore) async { + final leaderboardRepository = LeaderboardRepository(firestore); + return App(leaderboardRepository: leaderboardRepository); + }); } diff --git a/lib/main_production.dart b/lib/main_production.dart index 99927436..8673eff4 100644 --- a/lib/main_production.dart +++ b/lib/main_production.dart @@ -5,9 +5,13 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/app/app.dart'; import 'package:pinball/bootstrap.dart'; void main() { - bootstrap(() => const App()); + bootstrap((firestore) async { + final leaderboardRepository = LeaderboardRepository(firestore); + return App(leaderboardRepository: leaderboardRepository); + }); } diff --git a/lib/main_staging.dart b/lib/main_staging.dart index 99927436..8673eff4 100644 --- a/lib/main_staging.dart +++ b/lib/main_staging.dart @@ -5,9 +5,13 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/app/app.dart'; import 'package:pinball/bootstrap.dart'; void main() { - bootstrap(() => const App()); + bootstrap((firestore) async { + final leaderboardRepository = LeaderboardRepository(firestore); + return App(leaderboardRepository: leaderboardRepository); + }); } diff --git a/packages/leaderboard_repository/lib/src/leaderboard_repository.dart b/packages/leaderboard_repository/lib/src/leaderboard_repository.dart index 5a578358..5a5fa42c 100644 --- a/packages/leaderboard_repository/lib/src/leaderboard_repository.dart +++ b/packages/leaderboard_repository/lib/src/leaderboard_repository.dart @@ -131,6 +131,7 @@ class LeaderboardRepository { .orderBy('score') .get(); + // TODO(allisonryan0002): see if we can find a more performant solution. final documents = querySnapshot.docs; final ranking = documents.indexWhere( (document) => document.id == entryReference.id, diff --git a/packages/leaderboard_repository/lib/src/models/leaderboard_entry.dart b/packages/leaderboard_repository/lib/src/models/leaderboard_entry.dart index 16c17fb0..86cb2464 100644 --- a/packages/leaderboard_repository/lib/src/models/leaderboard_entry.dart +++ b/packages/leaderboard_repository/lib/src/models/leaderboard_entry.dart @@ -9,15 +9,12 @@ enum CharacterType { dash, /// Sparky character. - sparky, /// Android character. - android, /// Dino character. - dino, } diff --git a/pubspec.lock b/pubspec.lock index 79aa7138..71647cae 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -79,7 +79,7 @@ packages: source: hosted version: "1.1.0" cloud_firestore: - dependency: transitive + dependency: "direct main" description: name: cloud_firestore url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index abd78157..81b056b3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,6 +8,7 @@ environment: dependencies: bloc: ^8.0.2 + cloud_firestore: ^3.1.10 equatable: ^2.0.3 flame: ^1.1.0-releasecandidate.5 flame_bloc: ^1.2.0-releasecandidate.5 diff --git a/test/app/view/app_test.dart b/test/app/view/app_test.dart index d0fd36b9..5a6a249f 100644 --- a/test/app/view/app_test.dart +++ b/test/app/view/app_test.dart @@ -6,13 +6,25 @@ // https://opensource.org/licenses/MIT. import 'package:flutter_test/flutter_test.dart'; +import 'package:leaderboard_repository/leaderboard_repository.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:pinball/app/app.dart'; import 'package:pinball/landing/landing.dart'; +class MockLeaderboardRepository extends Mock implements LeaderboardRepository {} + void main() { group('App', () { + late LeaderboardRepository leaderboardRepository; + + setUp(() { + leaderboardRepository = MockLeaderboardRepository(); + }); + testWidgets('renders LandingPage', (tester) async { - await tester.pumpWidget(const App()); + await tester.pumpWidget( + App(leaderboardRepository: leaderboardRepository), + ); expect(find.byType(LandingPage), findsOneWidget); }); }); diff --git a/web/__/firebase/8.9.1/firebase-app.js b/web/__/firebase/8.9.1/firebase-app.js new file mode 100644 index 00000000..c806965c --- /dev/null +++ b/web/__/firebase/8.9.1/firebase-app.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).firebase=t()}(this,function(){"use strict";var r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};var n=function(){return(n=Object.assign||function(e){for(var t,n=1,r=arguments.length;na[0]&&t[1]=e.length?void 0:e)&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function f(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),a=[];try{for(;(void 0===t||0"})):"Error",e=this.serviceName+": "+e+" ("+o+").";return new c(o,e,i)},v);function v(e,t,n){this.service=e,this.serviceName=t,this.errors=n}var m=/\{\$([^}]+)}/g;function y(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function b(e,t){t=new g(e,t);return t.subscribe.bind(t)}var g=(I.prototype.next=function(t){this.forEachObserver(function(e){e.next(t)})},I.prototype.error=function(t){this.forEachObserver(function(e){e.error(t)}),this.close(t)},I.prototype.complete=function(){this.forEachObserver(function(e){e.complete()}),this.close()},I.prototype.subscribe=function(e,t,n){var r,i=this;if(void 0===e&&void 0===t&&void 0===n)throw new Error("Missing Observer.");void 0===(r=function(e,t){if("object"!=typeof e||null===e)return!1;for(var n=0,r=t;n=(null!=o?o:e.logLevel)&&a({level:R[t].toLowerCase(),message:i,args:n,type:e.name})}}(n[e])}var H=((H={})["no-app"]="No Firebase App '{$appName}' has been created - call Firebase App.initializeApp()",H["bad-app-name"]="Illegal App name: '{$appName}",H["duplicate-app"]="Firebase App named '{$appName}' already exists",H["app-deleted"]="Firebase App named '{$appName}' already deleted",H["invalid-app-argument"]="firebase.{$appName}() takes either no argument or a Firebase App instance.",H["invalid-log-argument"]="First argument to `onLog` must be null or a function.",H),V=new d("app","Firebase",H),B="@firebase/app",M="[DEFAULT]",U=((H={})[B]="fire-core",H["@firebase/analytics"]="fire-analytics",H["@firebase/app-check"]="fire-app-check",H["@firebase/auth"]="fire-auth",H["@firebase/database"]="fire-rtdb",H["@firebase/functions"]="fire-fn",H["@firebase/installations"]="fire-iid",H["@firebase/messaging"]="fire-fcm",H["@firebase/performance"]="fire-perf",H["@firebase/remote-config"]="fire-rc",H["@firebase/storage"]="fire-gcs",H["@firebase/firestore"]="fire-fst",H["fire-js"]="fire-js",H["firebase-wrapper"]="fire-js-all",H),W=new z("@firebase/app"),G=(Object.defineProperty($.prototype,"automaticDataCollectionEnabled",{get:function(){return this.checkDestroyed_(),this.automaticDataCollectionEnabled_},set:function(e){this.checkDestroyed_(),this.automaticDataCollectionEnabled_=e},enumerable:!1,configurable:!0}),Object.defineProperty($.prototype,"name",{get:function(){return this.checkDestroyed_(),this.name_},enumerable:!1,configurable:!0}),Object.defineProperty($.prototype,"options",{get:function(){return this.checkDestroyed_(),this.options_},enumerable:!1,configurable:!0}),$.prototype.delete=function(){var t=this;return new Promise(function(e){t.checkDestroyed_(),e()}).then(function(){return t.firebase_.INTERNAL.removeApp(t.name_),Promise.all(t.container.getProviders().map(function(e){return e.delete()}))}).then(function(){t.isDeleted_=!0})},$.prototype._getService=function(e,t){void 0===t&&(t=M),this.checkDestroyed_();var n=this.container.getProvider(e);return n.isInitialized()||"EXPLICIT"!==(null===(e=n.getComponent())||void 0===e?void 0:e.instantiationMode)||n.initialize(),n.getImmediate({identifier:t})},$.prototype._removeServiceInstance=function(e,t){void 0===t&&(t=M),this.container.getProvider(e).clearInstance(t)},$.prototype._addComponent=function(t){try{this.container.addComponent(t)}catch(e){W.debug("Component "+t.name+" failed to register with FirebaseApp "+this.name,e)}},$.prototype._addOrOverwriteComponent=function(e){this.container.addOrOverwriteComponent(e)},$.prototype.toJSON=function(){return{name:this.name,automaticDataCollectionEnabled:this.automaticDataCollectionEnabled,options:this.options}},$.prototype.checkDestroyed_=function(){if(this.isDeleted_)throw V.create("app-deleted",{appName:this.name_})},$);function $(e,t,n){var r=this;this.firebase_=n,this.isDeleted_=!1,this.name_=t.name,this.automaticDataCollectionEnabled_=t.automaticDataCollectionEnabled||!1,this.options_=h(void 0,e),this.container=new S(t.name),this._addComponent(new E("app",function(){return r},"PUBLIC")),this.firebase_.INTERNAL.components.forEach(function(e){return r._addComponent(e)})}G.prototype.name&&G.prototype.options||G.prototype.delete||console.log("dc");var K="8.9.1";function Y(a){var s={},l=new Map,c={__esModule:!0,initializeApp:function(e,t){void 0===t&&(t={});"object"==typeof t&&null!==t||(t={name:t});var n=t;void 0===n.name&&(n.name=M);t=n.name;if("string"!=typeof t||!t)throw V.create("bad-app-name",{appName:String(t)});if(y(s,t))throw V.create("duplicate-app",{appName:t});n=new a(e,n,c);return s[t]=n},app:u,registerVersion:function(e,t,n){var r=null!==(i=U[e])&&void 0!==i?i:e;n&&(r+="-"+n);var i=r.match(/\s|\//),e=t.match(/\s|\//);i||e?(n=['Unable to register library "'+r+'" with version "'+t+'":'],i&&n.push('library name "'+r+'" contains illegal characters (whitespace or "/")'),i&&e&&n.push("and"),e&&n.push('version name "'+t+'" contains illegal characters (whitespace or "/")'),W.warn(n.join(" "))):o(new E(r+"-version",function(){return{library:r,version:t}},"VERSION"))},setLogLevel:T,onLog:function(e,t){if(null!==e&&"function"!=typeof e)throw V.create("invalid-log-argument");x(e,t)},apps:null,SDK_VERSION:K,INTERNAL:{registerComponent:o,removeApp:function(e){delete s[e]},components:l,useAsService:function(e,t){return"serverAuth"!==t?t:null}}};function u(e){if(!y(s,e=e||M))throw V.create("no-app",{appName:e});return s[e]}function o(n){var e,r=n.name;if(l.has(r))return W.debug("There were multiple attempts to register component "+r+"."),"PUBLIC"===n.type?c[r]:null;l.set(r,n),"PUBLIC"===n.type&&(e=function(e){if("function"!=typeof(e=void 0===e?u():e)[r])throw V.create("invalid-app-argument",{appName:r});return e[r]()},void 0!==n.serviceProps&&h(e,n.serviceProps),c[r]=e,a.prototype[r]=function(){for(var e=[],t=0;tPinball + +