From 6bc301c11c41efc10d4e74085dac0edc781d2d59 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Wed, 22 Apr 2020 15:58:10 -0400 Subject: [PATCH 01/67] =?UTF-8?q?Removed=20the=20android=20play=20services?= =?UTF-8?q?=20config=20from=20git=20history=20and=20added=20file=20to=20.g?= =?UTF-8?q?itignore=20=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/android/.gitignore b/android/.gitignore index 6e1ec20..24ed0af 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -7,3 +7,4 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java distribution/*.jks key.properties +google-services.json From 15a08597987ec661986c4e2a1884b6f1941add05 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Thu, 23 Apr 2020 11:24:05 -0400 Subject: [PATCH 02/67] Re-enabled analytics Added firebase_messaging dependency Updated README.md --- README.md | 28 +++++++++++++- android/app/build.gradle | 1 + android/app/src/main/AndroidManifest.xml | 4 ++ ios/Flutter/.last_build_id | 2 +- ios/Podfile.lock | 38 +++++++++++++++++++ lib/app.dart | 4 +- .../authentication_bloc.dart | 2 +- lib/internal/analytics.dart | 29 ++++++++++++-- lib/ui/signin/bloc/sign_in_bloc.dart | 4 +- pubspec.lock | 14 +++++++ pubspec.yaml | 3 +- 11 files changed, 118 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e48432f..391228c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Apps Against Humanity - _WIP_ +# Apps Against Humanity A Flutter Cards Against Humanity app for Android, iOS, and Web @@ -10,6 +10,32 @@ Setup a [Firebase Project](https://firebase.com/) with an Android and iOS applic 1. Download the `google-services.json` file for your Android application to `android/app/google-services.json` 2. Download the `GoogleServices-Info.plist` file for your iOS application to `ios/Runner/GoogleServices-Info.plist` +3. Add the following contents to a file named `firebase_config.js` to the `web/` folder + +```javascript +// Your web app's Firebase configuration +var firebaseConfig = { + apiKey: "some_api_key", + authDomain: "some_firebase_project_id.firebaseapp.com", + databaseURL: "https://some_firebase_project_id.firebaseio.com", + projectId: "some_firebase_project_id", + storageBucket: "some_firebase_project_id.appspot.com", + messagingSenderId: "00000000000", + appId: "some_web_app_id", + measurementId: "some_analytics_id" +}; +// Initialize Firebase +firebase.initializeApp(firebaseConfig); +firebase.analytics(); +``` + +### Setup Functions + +Fork the repository here: https://github.com/52inc/AppsAgainstHumanity-Firebase and deploy the function to your firebase project using: + +```bash +firebase deploy --only functions +``` # Contributing diff --git a/android/app/build.gradle b/android/app/build.gradle index 53c7984..0e53716 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -73,6 +73,7 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.google.firebase:firebase-analytics:17.3.0' + implementation 'com.google.firebase:firebase-messaging:20.1.5' implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta04' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c220634..5c03b82 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -39,6 +39,10 @@ + + + + diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id index 3b3243f..06c5caa 100644 --- a/ios/Flutter/.last_build_id +++ b/ios/Flutter/.last_build_id @@ -1 +1 @@ -2679397143cb87e29b2b0b9d788d7ece \ No newline at end of file +5f8e23ba106f05148b26d487766e9033 \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e87a69b..4d9770b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -185,6 +185,8 @@ PODS: - Flutter - device_info (0.0.1): - Flutter + - Firebase/Analytics (6.15.0): + - Firebase/Core - Firebase/Auth (6.15.0): - Firebase/CoreOnly - FirebaseAuth (~> 6.4.2) @@ -199,9 +201,16 @@ PODS: - Firebase/Functions (6.15.0): - Firebase/CoreOnly - FirebaseFunctions (~> 2.5.1) + - Firebase/Messaging (6.15.0): + - Firebase/CoreOnly + - FirebaseMessaging (~> 4.2.0) - Firebase/Storage (6.15.0): - Firebase/CoreOnly - FirebaseStorage (~> 3.5.0) + - firebase_analytics (0.0.1): + - Firebase/Analytics (~> 6.0) + - Firebase/Core + - Flutter - firebase_auth (0.0.1): - Firebase/Auth (~> 6.3) - Firebase/Core @@ -213,6 +222,10 @@ PODS: - Flutter - firebase_core_web (0.1.0): - Flutter + - firebase_messaging (0.0.1): + - Firebase/Core + - Firebase/Messaging + - Flutter - firebase_storage (0.0.1): - Firebase/Storage - Flutter @@ -225,6 +238,7 @@ PODS: - GoogleUtilities/Network (~> 6.0) - "GoogleUtilities/NSData+zlib (~> 6.0)" - nanopb (= 0.3.9011) + - FirebaseAnalyticsInterop (1.5.0) - FirebaseAuth (6.4.2): - FirebaseAuthInterop (~> 1.0) - FirebaseCore (~> 6.2) @@ -270,6 +284,15 @@ PODS: - FirebaseInstallations (~> 1.0) - GoogleUtilities/Environment (~> 6.5) - GoogleUtilities/UserDefaults (~> 6.5) + - FirebaseMessaging (4.2.1): + - FirebaseAnalyticsInterop (~> 1.5) + - FirebaseCore (~> 6.6) + - FirebaseInstanceID (~> 4.3) + - GoogleUtilities/AppDelegateSwizzler (~> 6.5) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Reachability (~> 6.5) + - GoogleUtilities/UserDefaults (~> 6.5) + - Protobuf (>= 3.9.2, ~> 3.9) - FirebaseStorage (3.5.0): - FirebaseAuthInterop (~> 1.0) - FirebaseCore (~> 6.0) @@ -349,6 +372,7 @@ PODS: - path_provider_macos (0.0.1): - Flutter - PromisesObjC (1.2.8) + - Protobuf (3.11.4) - shared_preferences (0.0.1): - Flutter - shared_preferences_macos (0.0.1): @@ -365,10 +389,12 @@ DEPENDENCIES: - cloud_functions (from `.symlinks/plugins/cloud_functions/ios`) - cloud_functions_web (from `.symlinks/plugins/cloud_functions_web/ios`) - device_info (from `.symlinks/plugins/device_info/ios`) + - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) - firebase_auth_web (from `.symlinks/plugins/firebase_auth_web/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_core_web (from `.symlinks/plugins/firebase_core_web/ios`) + - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - firebase_storage (from `.symlinks/plugins/firebase_storage/ios`) - Flutter (from `Flutter`) - google_sign_in (from `.symlinks/plugins/google_sign_in/ios`) @@ -388,6 +414,7 @@ SPEC REPOS: - BoringSSL-GRPC - Firebase - FirebaseAnalytics + - FirebaseAnalyticsInterop - FirebaseAuth - FirebaseAuthInterop - FirebaseCore @@ -397,6 +424,7 @@ SPEC REPOS: - FirebaseFunctions - FirebaseInstallations - FirebaseInstanceID + - FirebaseMessaging - FirebaseStorage - GoogleAppMeasurement - GoogleDataTransport @@ -410,6 +438,7 @@ SPEC REPOS: - leveldb-library - nanopb - PromisesObjC + - Protobuf EXTERNAL SOURCES: apple_sign_in: @@ -424,6 +453,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/cloud_functions_web/ios" device_info: :path: ".symlinks/plugins/device_info/ios" + firebase_analytics: + :path: ".symlinks/plugins/firebase_analytics/ios" firebase_auth: :path: ".symlinks/plugins/firebase_auth/ios" firebase_auth_web: @@ -432,6 +463,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_core/ios" firebase_core_web: :path: ".symlinks/plugins/firebase_core_web/ios" + firebase_messaging: + :path: ".symlinks/plugins/firebase_messaging/ios" firebase_storage: :path: ".symlinks/plugins/firebase_storage/ios" Flutter: @@ -466,12 +499,15 @@ SPEC CHECKSUMS: cloud_functions_web: ec883775f4a3b3fad943918d2cdad4fbf51cdbf0 device_info: cbf09d2ec12aa7110e0b09fabe54b5bd6c8efe74 Firebase: 5d77105d9740a07ca6b16927ca971db7e860faaf + firebase_analytics: dacdcfc524d722fff13dcff942f0dfa47e6be567 firebase_auth: 6838289663da45c488af0a82c246928588fcddc5 firebase_auth_web: 0955c07bcc06e84af76b9d4e32e6f31518f2d7de firebase_core: dc539d4bdc69894823e4a6e557034a9e48960f21 firebase_core_web: d501d8b946b60c8af265428ce483b0fff5ad52d1 + firebase_messaging: cffb57ce40958c6204f03fb0c81713e4cd1e240c firebase_storage: 5e931af5cdef32331676c659bdd1ebcaba9dc78a FirebaseAnalytics: e83e64b1231dedcd9ddd4bdecd9bcfd6ba341679 + FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae FirebaseAuth: ce45d7c5d46bed90159f3a73b6efbe8976ed3573 FirebaseAuthInterop: 0ffa57668be100582bb7643d4fcb7615496c41fc FirebaseCore: 4aeb81ff53dcd9a3634ca725dc1fb8c2a4622046 @@ -481,6 +517,7 @@ SPEC CHECKSUMS: FirebaseFunctions: 5af7c35d1c5e41608fecbb667eb6c4e672e318d0 FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b FirebaseInstanceID: 6668efc1655a4052c083f287a7141f1ead12f9c2 + FirebaseMessaging: 9e23f9d603b1fb71ccbeb48d79bf0d4ba0272c84 FirebaseStorage: 6c5263796af3b1be82ed173598aade47535fe125 Flutter: 0e3d915762c693b495b44d77113d4970485de6ec google_sign_in: f32920a589fdf4ab2918ec6dc5e5b0d5b8040ff5 @@ -500,6 +537,7 @@ SPEC CHECKSUMS: path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 + Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 diff --git a/lib/app.dart b/lib/app.dart index 3fdd1f1..59b24ae 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -5,7 +5,7 @@ import 'package:appsagainsthumanity/ui/signin/sign_in_screen.dart'; import 'package:appsagainsthumanity/ui/terms_screen.dart'; import 'package:flutter/material.dart'; -//import 'package:firebase_analytics/observer.dart'; +import 'package:firebase_analytics/observer.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'authentication_bloc/authentication_bloc.dart'; @@ -27,7 +27,7 @@ class App extends StatelessWidget { const Locale('en'), // English ], navigatorObservers: [ -// FirebaseAnalyticsObserver(analytics: Analytics.firebaseAnalytics), + FirebaseAnalyticsObserver(analytics: Analytics()), Routes.routeObserver ], home: BlocBuilder( diff --git a/lib/authentication_bloc/authentication_bloc.dart b/lib/authentication_bloc/authentication_bloc.dart index bd2f4ab..475a2a8 100644 --- a/lib/authentication_bloc/authentication_bloc.dart +++ b/lib/authentication_bloc/authentication_bloc.dart @@ -74,7 +74,7 @@ class AuthenticationBloc extends Bloc Stream _mapLoggedOutToState() async* { yield Unauthenticated(); -// await Analytics.firebaseAnalytics.logEvent(name: "logout"); + await Analytics().logEvent(name: "logout"); _userRepository.signOut(); } } diff --git a/lib/internal/analytics.dart b/lib/internal/analytics.dart index ab8cfc1..58c396e 100644 --- a/lib/internal/analytics.dart +++ b/lib/internal/analytics.dart @@ -1,7 +1,30 @@ -//import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:firebase_analytics/firebase_analytics.dart'; -class Analytics { +class Analytics with FirebaseAnalytics { Analytics._(); -// static FirebaseAnalytics firebaseAnalytics = FirebaseAnalytics(); + static Analytics _instance = Analytics._(); + static FirebaseAnalytics _firebaseAnalytics = FirebaseAnalytics(); + + factory Analytics() { + return _instance; + } + + @override + Future setCurrentScreen({String screenName, String screenClassOverride = 'Flutter'}) { + return _firebaseAnalytics?.setCurrentScreen(screenName: screenName, screenClassOverride: 'Flutter') + ?? Future.value(); + } + + @override + Future logEvent({String name, Map parameters}) { + return _firebaseAnalytics?.logEvent(name: name, parameters: parameters) + ?? Future.value(); + } + + @override + Future logLogin({String loginMethod}) { + return _firebaseAnalytics?.logLogin(loginMethod: loginMethod) + ?? Future.value(); + } } diff --git a/lib/ui/signin/bloc/sign_in_bloc.dart b/lib/ui/signin/bloc/sign_in_bloc.dart index 1d460d3..b0a0474 100644 --- a/lib/ui/signin/bloc/sign_in_bloc.dart +++ b/lib/ui/signin/bloc/sign_in_bloc.dart @@ -30,7 +30,7 @@ class SignInBloc extends Bloc { yield SignInState.loading(); try { await _userRepository.signInWithGoogle(); - //await Analytics.firebaseAnalytics.logLogin(loginMethod: "google"); + await Analytics().logLogin(loginMethod: "google"); yield SignInState.success(); } catch (e, stacktrace) { Logger("SignInBloc").fine("Error signing in: $e\n$stacktrace"); @@ -42,7 +42,7 @@ class SignInBloc extends Bloc { yield SignInState.loading(); try { await _userRepository.signInWithApple(); - //await Analytics.firebaseAnalytics.logLogin(loginMethod: "google"); + await Analytics().logLogin(loginMethod: "apple"); yield SignInState.success(); } catch (e, stacktrace) { Logger("SignInBloc").fine("Error signing in: $e\n$stacktrace"); diff --git a/pubspec.lock b/pubspec.lock index 6379649..27b175b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -253,6 +253,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "7.3.0" + firebase_analytics: + dependency: "direct main" + description: + name: firebase_analytics + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.11" firebase_auth: dependency: "direct main" description: @@ -295,6 +302,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.1+2" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.13" firebase_storage: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 688dacb..bde4115 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,8 +46,9 @@ dependencies: # FlutterFire firebase_core: 0.4.4 firebase_auth: 0.15.3 -# firebase_analytics: ^5.0.11 + firebase_analytics: ^5.0.11 firebase_storage: ^3.1.3 + firebase_messaging: ^6.0.13 cloud_firestore: ^0.13.4+1 cloud_functions: ^0.4.2+3 google_sign_in: ^4.4.1 From d84add441e8eaee0c9ad6efaad9ec6095b923715 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Thu, 23 Apr 2020 11:35:42 -0400 Subject: [PATCH 03/67] Updated README.md --- README.md | 28 +++++++++++++++++++++++++++- example.config.json | 5 +++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 example.config.json diff --git a/README.md b/README.md index 391228c..261e4f8 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,35 @@ firebase.initializeApp(firebaseConfig); firebase.analytics(); ``` +### Setup Config + +Add your own configuration file to the `/lib` folder. Use this example: `example.config.json` + +```json +{ + "privacyPolicyUrl": "https://example.com/privacy.html", + "termsOfServiceUrl": "https://example.com/tos.html", + "sourceUrl": "https://github.com/52inc/AppsAgainstHumanity" +} +``` + +Then run + +```shell +flutter generate +``` + +### Generate JSON models + +Just run this: + +```shell +flutter packages pub run build_runner build --delete-conflicting-outputs +``` + ### Setup Functions -Fork the repository here: https://github.com/52inc/AppsAgainstHumanity-Firebase and deploy the function to your firebase project using: +Fork the repository here: https://github.com/52inc/AppsAgainstHumanity-Firebase and deploy the functions to your firebase project using: ```bash firebase deploy --only functions diff --git a/example.config.json b/example.config.json new file mode 100644 index 0000000..85d5478 --- /dev/null +++ b/example.config.json @@ -0,0 +1,5 @@ +{ + "privacyPolicyUrl": "https://example.com/privacy.html", + "termsOfServiceUrl": "https://example.com/tos.html", + "sourceUrl": "https://github.com/52inc/AppsAgainstHumanity" +} From 9be58c34db715a62cc3ebbba3cba41cfb01149da Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Thu, 23 Apr 2020 22:58:54 -0400 Subject: [PATCH 04/67] Started push message integrating --- android/app/src/main/AndroidManifest.xml | 17 ++++ .../res/drawable-hdpi/ic_notification.png | Bin 0 -> 370 bytes .../res/drawable-mdpi/ic_notification.png | Bin 0 -> 254 bytes .../res/drawable-xhdpi/ic_notification.png | Bin 0 -> 368 bytes .../res/drawable-xxhdpi/ic_notification.png | Bin 0 -> 522 bytes .../res/drawable-xxxhdpi/ic_notification.png | Bin 0 -> 629 bytes android/app/src/main/res/values/colors.xml | 4 + android/app/src/main/res/values/strings.xml | 4 + ios/Flutter/.last_build_id | 2 +- ios/Runner/AppDelegate.swift | 5 ++ ios/Runner/Info.plist | 5 ++ ios/Runner/Runner.entitlements | 2 + lib/data/app_preferences.dart | 18 +++++ .../features/devices/device_repository.dart | 73 ++++++++++++++++++ lib/data/features/users/user_repository.dart | 5 ++ lib/internal/push.dart | 50 ++++++++++++ lib/main.dart | 4 + pubspec.lock | 7 ++ pubspec.yaml | 1 + 19 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 android/app/src/main/res/drawable-hdpi/ic_notification.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_notification.png create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_notification.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_notification.png create mode 100644 android/app/src/main/res/drawable-xxxhdpi/ic_notification.png create mode 100644 android/app/src/main/res/values/colors.xml create mode 100644 android/app/src/main/res/values/strings.xml create mode 100644 lib/data/features/devices/device_repository.dart create mode 100644 lib/internal/push.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5c03b82..bd8afec 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -44,10 +44,27 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable-hdpi/ic_notification.png b/android/app/src/main/res/drawable-hdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..9177dd83cd8f53f6e5ba81d942bbf03eea1ad581 GIT binary patch literal 370 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i1|)m0d?MK literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/ic_notification.png b/android/app/src/main/res/drawable-mdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..c92982c080236b545aca4ca3f079263cdc1a5a2c GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyy~STEL88gA~qMxhWJ#Wq7(chFAzD zCoEw6z%uKnM8|*OPRA_ADgy@1wgWoMFAp*pc!u&w`Y=wAno+euM|qOe1Qtt%95!J~ zE6$gVmZI~oPuR;6%x_lyp|UqC(IiV?;grJ%93~r7Io*ik5smm7uvX&WhCUHS2F>{< Ud;8v9s{z^V>FVdQ&MBb@05LB<;Q#;t literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xhdpi/ic_notification.png b/android/app/src/main/res/drawable-xhdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..7b495733b5016d92cef91c40c273914638c2b2e0 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSWdSpS4N|DZ_xKNxy5;HO7!twx zHo}qbkO7bOo@D)|`cHgu#(sMkAKeyGvXna8GC?^s>h_VcyWckSExhskKvJrC_Uf(I zpRoBPG%ptMsEL}btMaIEu?p9#
Q@_|POo#wzH}gax)`$D==-)jgdTdZb)RJNg;l+|_w6Qc`xMMlaTm-gY+s z|1^cPjWUy*&oG*;H&$t%!JHG9{z6}-YD1{coc|9_ox4!0q}Z%g`1ejSa~Nx21;>jY l=h@XZc^V6B#E80N5^>p=fS?83{1OS_Iei{G( literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_notification.png b/android/app/src/main/res/drawable-xxhdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..bf3c52bfc98127035fbe89c731f446148a5a5768 GIT binary patch literal 522 zcmeAS@N?(olHy`uVBq!ia0vp^W+2SL1|)l2v+e>Z#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyy~yTfmH9gA{&t)>{HpR^sX67?Q#I zHtKZmAp-&4>IX-}Hn0{gWvE&x^3KFT^-b+eb;C<1W>usv7TEuyH|3{u&i?+j6P=cM zDzavX+>pGqc2A@!)j`+NqciUBx*>f&S^rJsqYrhT zFWGHebG<;YdYZ+a`9i_E;iu%57|ft~yh-AV z?|I?Wy&+Bi^aZC|?76SA#b4*Os&I)eh&Ny8_0D8()eMVkb2@^<%x0)fEZPl5iHG;eQ&z<^k7Q_$`czviN y{P^rYPdM6gKPem9cNXLo&;M#^c{L~@k#*jb?G>LD&i-!$#e=7-pUXO@geCxgd! literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_notification.png b/android/app/src/main/res/drawable-xxxhdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..acf5ce5290352938b541a3b98bb279e171c982c9 GIT binary patch literal 629 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(<^v49!D1}U6i==L6{?7pXqV@L(# z+c^h)nGFS8nc4j=Fv?yK`&+P3jL&0=Bl8x9aF%~fGD@m-)2?}6d)Awx_|5wD%;)*+ z-f!NOwXvuuG;ZX$GqptCt6KN%-Yx5Uw?CN?Gr71ZUU%K8x$@q>b#h+^_&uF|C)hdb zrPR)c-kHnU`q%Mu)}Fi<=1J50Cw)14>Bvb{|I~)tOPkkx`1xeY9gY7- zoi8&)w`|t8+8Oxo_dfUfb31;@>6V=BHen9d-#E?Jsv)gUi+Q?)&)x{%a|GU!XP5Y+~AjnDmYIkx5sJPw4Mw zm}R+&Cnm|@E0d6b07E1@CkIEvnr2k<7$=lYXlP(KVddfAz>p%VqM*RAiH}I-PsG@t zF5Dc+oSn`1#Y}r{kodK0OfM>v9AdM6U+-_-K1-SZ;br^czGcgoXiTh literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..82e35de --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #AB47BC + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..e2ed97d --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + aah_game_notifications + diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id index 06c5caa..a3d20ae 100644 --- a/ios/Flutter/.last_build_id +++ b/ios/Flutter/.last_build_id @@ -1 +1 @@ -5f8e23ba106f05148b26d487766e9033 \ No newline at end of file +2846662d6d846333266c8c97fe0ab973 \ No newline at end of file diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4..be7ca85 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -8,6 +8,11 @@ import Flutter didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) + + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + } + return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 3c14885..1972f42 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -35,6 +35,11 @@ public.app-category.card-games LSRequiresIPhoneOS + UIBackgroundModes + + fetch + remote-notification + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements index a812db5..80b5221 100644 --- a/ios/Runner/Runner.entitlements +++ b/ios/Runner/Runner.entitlements @@ -2,6 +2,8 @@ + aps-environment + development com.apple.developer.applesignin Default diff --git a/lib/data/app_preferences.dart b/lib/data/app_preferences.dart index 272ac2d..a4bb579 100644 --- a/lib/data/app_preferences.dart +++ b/lib/data/app_preferences.dart @@ -1,4 +1,5 @@ import 'package:shared_preferences/shared_preferences.dart'; +import 'package:uuid/uuid.dart'; class AppPreferences { static AppPreferences _instance = AppPreferences._internal(); @@ -11,6 +12,8 @@ class AppPreferences { } static const KEY_AGREE_TO_TERMS = "agree_to_terms_of_service"; + static const KEY_PUSH_TOKEN = "push_token"; + static const KEY_DEVICE_ID = "device_id"; SharedPreferences _prefs; @@ -23,7 +26,22 @@ class AppPreferences { bool get agreedToTermsOfService => _prefs.getBool(KEY_AGREE_TO_TERMS) ?? false; set agreedToTermsOfService(bool agreed) => _prefs.setBool(KEY_AGREE_TO_TERMS, agreed); + String get pushToken => _prefs.getString(KEY_PUSH_TOKEN); + set pushToken(String token) => _prefs.setString(KEY_PUSH_TOKEN, token); + + String get deviceId { + var dId = _prefs.getString(KEY_DEVICE_ID); + if (dId == null) { + dId = Uuid().v4(); + deviceId = dId; + } + return dId; + } + set deviceId(String deviceId) => _prefs.setString(KEY_DEVICE_ID, deviceId); + clear() async { + var _deviceId = deviceId; await _prefs.clear(); + deviceId = _deviceId; } } diff --git a/lib/data/features/devices/device_repository.dart b/lib/data/features/devices/device_repository.dart new file mode 100644 index 0000000..f87ad5b --- /dev/null +++ b/lib/data/features/devices/device_repository.dart @@ -0,0 +1,73 @@ +import 'dart:io'; + +import 'package:appsagainsthumanity/data/app_preferences.dart'; +import 'package:appsagainsthumanity/data/firestore.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:device_info/device_info.dart'; +import 'package:firebase_auth/firebase_auth.dart'; + +class DeviceRepository { + static DeviceRepository _instance = DeviceRepository._(); + + final FirebaseAuth _auth = FirebaseAuth.instance; + final Firestore _db = Firestore.instance; + final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + + DeviceRepository._(); + + factory DeviceRepository() { + return _instance; + } + + void updatePushToken(String token) async { + AppPreferences().pushToken = token; + var currentUser = await _auth.currentUser(); + if (currentUser != null) { + // If current token is not null, attempt to pull existing device info and copy it to new push token + var document = _db.collection(FirebaseConstants.COLLECTION_USERS) + .document(currentUser.uid) + .collection(FirebaseConstants.COLLECTION_DEVICES) + .document(AppPreferences().deviceId); + + var snapshot = await document.get(); + if (snapshot.exists) { + await document.updateData({ + "token": token + }); + } else { + var newDevice = await _createNewDevice(token); + await document.setData(newDevice); + } + } + } + + Future> _createNewDevice(String token) async { + return { + "token": token, + "platform": _getPlatform(), + "info": await _getDeviceInfo(), + "createdAt": Timestamp.now(), + "updatedAt": Timestamp.now() + }; + } + + String _getPlatform() { + if (Platform.isAndroid) { + return "android"; + } else if (Platform.isIOS) { + return "ios"; + } else { + return Platform.operatingSystem; + } + } + + Future _getDeviceInfo() async { + if (Platform.isAndroid) { + var androidInfo = await deviceInfo.androidInfo; + return "${androidInfo.manufacturer}/${androidInfo.model}/${androidInfo.product}/isPhysicalDevice(${androidInfo.isPhysicalDevice})/sdk(${androidInfo.version.sdkInt})"; + } else { + var iosInfo = await deviceInfo.iosInfo; + return "${iosInfo.model}/${iosInfo.systemName}/${iosInfo.systemVersion}"; + } + } +} diff --git a/lib/data/features/users/user_repository.dart b/lib/data/features/users/user_repository.dart index b416370..f23e82f 100644 --- a/lib/data/features/users/user_repository.dart +++ b/lib/data/features/users/user_repository.dart @@ -1,8 +1,10 @@ import 'package:apple_sign_in/apple_sign_in.dart'; import 'package:appsagainsthumanity/data/app_preferences.dart'; +import 'package:appsagainsthumanity/data/features/devices/device_repository.dart'; import 'package:appsagainsthumanity/data/features/game/model/player.dart'; import 'package:appsagainsthumanity/data/features/users/model/user.dart'; import 'package:appsagainsthumanity/data/firestore.dart'; +import 'package:appsagainsthumanity/internal/push.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:google_sign_in/google_sign_in.dart'; @@ -118,6 +120,9 @@ class UserRepository { "Signed-in! User(id=${result.user.uid}, name=${result.user.displayName}, photoUrl=${result.user .photoUrl})"); + // Excellent! Let's create our device + await PushNotifications().checkAndUpdateToken(); + return User( id: result.user.uid, name: result.user.displayName, diff --git a/lib/internal/push.dart b/lib/internal/push.dart new file mode 100644 index 0000000..b9bad07 --- /dev/null +++ b/lib/internal/push.dart @@ -0,0 +1,50 @@ +import 'dart:convert'; + +import 'package:appsagainsthumanity/data/app_preferences.dart'; +import 'package:appsagainsthumanity/data/features/devices/device_repository.dart'; +import 'package:appsagainsthumanity/ui/settings/widgets/preference.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:logging/logging.dart'; + +class PushNotifications { + PushNotifications._(); + + static PushNotifications _instance = PushNotifications._(); + + factory PushNotifications() { + return _instance; + } + + void setup() { + // Setup firebase messaging + final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); + _firebaseMessaging.onTokenRefresh.listen((token) { + DeviceRepository().updatePushToken(token); + }); + _firebaseMessaging.configure( + onLaunch: (Map message) async { + print("onLaunch()"); + print(JsonEncoder().convert(message)); + }, + onMessage: (Map message) async { + print("onMessage()"); + print(JsonEncoder().convert(message)); + }, + onResume: (Map message) async { + print("onResume()"); + print(JsonEncoder().convert(message)); + } + ); + + checkAndUpdateToken(); + } + + Future checkAndUpdateToken() async { + final FirebaseMessaging messaging = FirebaseMessaging(); + String token = await messaging.getToken(); + if (token != AppPreferences().pushToken) { + Logger.root.fine("FCM Token is different from what is stored in preferences, updating device..."); + DeviceRepository().updatePushToken(token); + } + } +} diff --git a/lib/main.dart b/lib/main.dart index 7b420d5..023aafa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:appsagainsthumanity/data/features/game/firestore_game_repository import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/data/features/users/user_repository.dart'; import 'package:appsagainsthumanity/internal/logging_bloc_delegate.dart'; +import 'package:appsagainsthumanity/internal/push.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:logging/logging.dart'; @@ -22,6 +23,9 @@ void main() async { print('${rec.level.name}: ${rec.time}: ${rec.message}'); }); + // Setup Push Notifications + PushNotifications().setup(); + runApp(buildRepositoryProvider(BlocProvider( create: (context) { return AuthenticationBloc( diff --git a/pubspec.lock b/pubspec.lock index 27b175b..2c791e3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -838,6 +838,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.6" + uuid: + dependency: "direct main" + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index bde4115..c58d212 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: device_info: ^0.4.1+5 path_provider: ^1.6.5 path: ^1.6.4 + uuid: ^2.0.4 # Architecture and Tools bloc: ^3.0.0 From f8d5442b038fd380384796bcf2a5f4bada6b95cf Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Fri, 24 Apr 2020 12:41:27 -0400 Subject: [PATCH 05/67] Updated downvotes structure to match server Added a flag to remove user's from game ui if marked as inactive (this leads to support of kicking, leaving, deleting players from games) --- .../game/firestore_game_repository.dart | 18 +- lib/data/features/game/game_repository.dart | 3 + lib/data/features/game/model/player.dart | 10 +- lib/data/features/game/model/turn.dart | 9 +- lib/data/features/users/user_repository.dart | 1 - lib/data/firestore.dart | 2 + lib/internal/push.dart | 1 - lib/ui/game/bloc/game_bloc.dart | 12 ++ lib/ui/game/bloc/game_event.dart | 10 + lib/ui/game/bloc/game_state.dart | 177 +++++++++--------- .../screens/gameplay/game_play_screen.dart | 3 - .../gameplay/widget/judge/judge_bar.dart | 2 +- .../gameplay/widget/judge/judge_dredd.dart | 1 - .../screens/gameplay/widget/player_list.dart | 4 +- .../gameplay/widget/prompt_container.dart | 1 - .../gameplay/widget/turn_winner_sheet.dart | 1 - .../widget/waiting_player_responses.dart | 5 +- .../screens/waiting/waiting_room_screen.dart | 1 - regenerate.sh | 6 + test/widget_test.dart | 7 +- 20 files changed, 160 insertions(+), 114 deletions(-) create mode 100755 regenerate.sh diff --git a/lib/data/features/game/firestore_game_repository.dart b/lib/data/features/game/firestore_game_repository.dart index bf12729..6fbc9e0 100644 --- a/lib/data/features/game/firestore_game_repository.dart +++ b/lib/data/features/game/firestore_game_repository.dart @@ -111,6 +111,18 @@ class FirestoreGameRepository extends GameRepository { return collection.snapshots().map((snapshots) => snapshots.documents.map((e) => Player.fromDocument(e)).toList()); } + @override + Stream> observeDownvotes(String gameDocumentId) { + var collection = db + .collection(FirebaseConstants.COLLECTION_GAMES) + .document(gameDocumentId) + .collection(FirebaseConstants.COLLECTION_DOWNVOTES) + .document(FirebaseConstants.DOCUMENT_TALLY); + + return collection.snapshots() + .map((snapshot) => List.from(snapshot['votes'] ?? [])); + } + @override Future addRandoCardrissian(String gameDocumentId) async { var document = db @@ -178,10 +190,12 @@ class FirestoreGameRepository extends GameRepository { return currentUserOrThrow((firebaseUser) async { var gameDocument = db .collection(FirebaseConstants.COLLECTION_GAMES) - .document(gameDocumentId); + .document(gameDocumentId) + .collection(FirebaseConstants.COLLECTION_DOWNVOTES) + .document(FirebaseConstants.DOCUMENT_TALLY); await gameDocument.updateData({ - 'turn.downvotes': FieldValue.arrayUnion([firebaseUser.uid]) + 'votes': FieldValue.arrayUnion([firebaseUser.uid]) }); }); } diff --git a/lib/data/features/game/game_repository.dart b/lib/data/features/game/game_repository.dart index e0a7ebf..e59ae37 100644 --- a/lib/data/features/game/game_repository.dart +++ b/lib/data/features/game/game_repository.dart @@ -27,6 +27,9 @@ abstract class GameRepository { /// [gameDocumentId] Stream> observePlayers(String gameDocumentId); + /// Observe any changes to the downvote tally by it's [gameDocumentId] + Stream> observeDownvotes(String gameDocumentId); + /// Add Rando Cardrissian to the game /// [gid] the game to add him to Future addRandoCardrissian(String gameDocumentId); diff --git a/lib/data/features/game/model/player.dart b/lib/data/features/game/model/player.dart index 063f0df..3c4c73a 100644 --- a/lib/data/features/game/model/player.dart +++ b/lib/data/features/game/model/player.dart @@ -18,6 +18,9 @@ class Player { @JsonKey(defaultValue: false) final bool isRandoCardrissian; + @JsonKey(defaultValue: false) + final bool isInactive; + @JsonKey(includeIfNull: false) final List hand; @@ -30,8 +33,10 @@ class Player { this.avatarUrl, this.hand, this.prizes, - bool isRandoCardrissian = false - }) : isRandoCardrissian = isRandoCardrissian; + bool isRandoCardrissian = false, + bool isInactive = false + }) : isRandoCardrissian = isRandoCardrissian, + isInactive = isInactive; factory Player.fromDocument(DocumentSnapshot documentSnapshot) => Player.fromJson(documentSnapshot.data); @@ -47,6 +52,7 @@ class Player { name: $name, avatarUrl: $avatarUrl, isRandoCardrissian: $isRandoCardrissian, + isInactive: $isInactive, hand: $hand, prizes: $prizes }'''; diff --git a/lib/data/features/game/model/turn.dart b/lib/data/features/game/model/turn.dart index 4ebefed..f8bfa40 100644 --- a/lib/data/features/game/model/turn.dart +++ b/lib/data/features/game/model/turn.dart @@ -2,7 +2,6 @@ import 'package:appsagainsthumanity/data/features/cards/model/prompt_card.dart'; import 'package:appsagainsthumanity/data/features/cards/model/response_card.dart'; import 'package:appsagainsthumanity/data/features/game/model/turn_winner.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:kt_dart/kt.dart'; import 'package:meta/meta.dart'; part 'turn.g.dart'; @@ -18,9 +17,6 @@ class Turn { @JsonKey(toJson: _responsesToJson) final Map> responses; - @JsonKey(nullable: true) - final Set downvotes; - @JsonKey(nullable: true, toJson: turnWinnerToJson) final TurnWinner winner; @@ -28,9 +24,8 @@ class Turn { @required this.judgeId, @required this.promptCard, @required this.responses, - Set downvotes, this.winner, - }) : downvotes = downvotes; + }); factory Turn.fromJson(Map json) => _$TurnFromJson(json); @@ -40,14 +35,12 @@ class Turn { String judgeId, PromptCard promptCard, Map> responses, - Set downvotes, TurnWinner winner, }) { return Turn( judgeId: judgeId ?? this.judgeId, promptCard: promptCard ?? this.promptCard, responses: responses ?? this.responses, - downvotes: downvotes ?? this.downvotes, winner: winner ?? this.winner); } } diff --git a/lib/data/features/users/user_repository.dart b/lib/data/features/users/user_repository.dart index f23e82f..adf45b9 100644 --- a/lib/data/features/users/user_repository.dart +++ b/lib/data/features/users/user_repository.dart @@ -1,6 +1,5 @@ import 'package:apple_sign_in/apple_sign_in.dart'; import 'package:appsagainsthumanity/data/app_preferences.dart'; -import 'package:appsagainsthumanity/data/features/devices/device_repository.dart'; import 'package:appsagainsthumanity/data/features/game/model/player.dart'; import 'package:appsagainsthumanity/data/features/users/model/user.dart'; import 'package:appsagainsthumanity/data/firestore.dart'; diff --git a/lib/data/firestore.dart b/lib/data/firestore.dart index 3a686ab..4b2689f 100644 --- a/lib/data/firestore.dart +++ b/lib/data/firestore.dart @@ -8,8 +8,10 @@ class FirebaseConstants { static const COLLECTION_CARD_SETS = "cardSets"; static const COLLECTION_GAMES = "games"; static const COLLECTION_PLAYERS = "players"; + static const COLLECTION_DOWNVOTES = "downvotes"; static const DOCUMENT_RANDO_CARDRISSIAN = "rando-cardrissian"; + static const DOCUMENT_TALLY = "tally"; } class UserNotFoundException { } diff --git a/lib/internal/push.dart b/lib/internal/push.dart index b9bad07..f77e2e0 100644 --- a/lib/internal/push.dart +++ b/lib/internal/push.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:appsagainsthumanity/data/app_preferences.dart'; import 'package:appsagainsthumanity/data/features/devices/device_repository.dart'; -import 'package:appsagainsthumanity/ui/settings/widgets/preference.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:logging/logging.dart'; diff --git a/lib/ui/game/bloc/game_bloc.dart b/lib/ui/game/bloc/game_bloc.dart index a015f04..22e8cfa 100644 --- a/lib/ui/game/bloc/game_bloc.dart +++ b/lib/ui/game/bloc/game_bloc.dart @@ -15,6 +15,7 @@ class GameBloc extends Bloc { StreamSubscription _gameSubscription; StreamSubscription _playersSubscription; + StreamSubscription _downvoteSubscription; GameBloc(this.initialGame, this.gameRepository); @@ -31,6 +32,8 @@ class GameBloc extends Bloc { yield* _mapGameUpdatedToState(event); } else if (event is PlayersUpdated) { yield* _mapPlayersUpdatedToState(event); + } else if (event is DownvotesUpdated) { + yield* _mapDownvotesUpdatedToState(event); } else if (event is StartGame) { yield* _mapStartGameToState(); } else if (event is ClearError) { @@ -61,6 +64,11 @@ class GameBloc extends Bloc { _playersSubscription = gameRepository.observePlayers(event.gameId).listen((players) { add(PlayersUpdated(players)); }); + + _downvoteSubscription?.cancel(); + _downvoteSubscription = gameRepository.observeDownvotes(event.gameId).listen((downvotes) { + add(DownvotesUpdated(downvotes)); + }); } Stream _mapUserUpdatedToState(UserUpdated event) async* { @@ -75,6 +83,10 @@ class GameBloc extends Bloc { yield state.copyWith(players: event.players, isLoading: false); } + Stream _mapDownvotesUpdatedToState(DownvotesUpdated event) async* { + yield state.copyWith(downvotes: event.downvotes, isLoading: false); + } + Stream _mapStartGameToState() async* { try { yield state.copyWith(isSubmitting: true); diff --git a/lib/ui/game/bloc/game_event.dart b/lib/ui/game/bloc/game_event.dart index 01554ae..fd3b1c1 100644 --- a/lib/ui/game/bloc/game_event.dart +++ b/lib/ui/game/bloc/game_event.dart @@ -59,6 +59,16 @@ class PlayersUpdated extends GameEvent { List get props => [players]; } +@immutable +class DownvotesUpdated extends GameEvent { + final List downvotes; + + DownvotesUpdated(this.downvotes); + + @override + List get props => [downvotes]; +} + class DownvotePrompt extends GameEvent { } @immutable diff --git a/lib/ui/game/bloc/game_state.dart b/lib/ui/game/bloc/game_state.dart index be3c6da..ac7b892 100644 --- a/lib/ui/game/bloc/game_state.dart +++ b/lib/ui/game/bloc/game_state.dart @@ -6,114 +6,121 @@ import 'package:meta/meta.dart'; @immutable class GameViewState { - /* FIELDS */ - final String userId; - final Game game; - final List players; - final List selectedCards; - final bool isSubmitting; - final bool isLoading; - final String error; + /* FIELDS */ + final String userId; + final Game game; + final List players; + final List selectedCards; + final List downvotes; + final bool isSubmitting; + final bool isLoading; + final String error; - /* PROPERTIES */ - bool get isOurGame => userId == game?.ownerId; + /* PROPERTIES */ + bool get isOurGame => userId == game?.ownerId; - Player get currentJudge => players?.firstWhere((p) => p.id == game.turn?.judgeId); + Player get currentJudge => players?.firstWhere((p) => p.id == game.turn?.judgeId); - /// Get the current prompt card text with any macros computed from the text string. For now this is just - /// the a simple replace of the judge's name for its specific replacer text. - /// TODO: Extract this into a tool that can take a configurable macro list for smart injecting text into prompts - String get currentPromptText { - var prompt = game.turn?.promptCard; - var judge = currentJudge; - if (judge != null && prompt != null) { - return prompt.text.replaceAll("{JUDGE_NAME}", judge.name); - } else if (prompt != null) { - return prompt.text; - } else { - return ""; - } + /// Get the current prompt card text with any macros computed from the text string. For now this is just + /// the a simple replace of the judge's name for its specific replacer text. + /// TODO: Extract this into a tool that can take a configurable macro list for smart injecting text into prompts + String get currentPromptText { + var prompt = game.turn?.promptCard; + var judge = currentJudge; + if (judge != null && prompt != null) { + return prompt.text.replaceAll("{JUDGE_NAME}", judge.name); + } else if (prompt != null) { + return prompt.text; + } else { + return ""; } + } - Player get currentPlayer => players?.firstWhere((p) => p.id == userId); + Player get currentPlayer => players?.firstWhere((p) => p.id == userId); - Player get winner => players?.firstWhere((p) => p.id == game.winner); + Player get winner => players?.firstWhere((p) => p.id == game.winner); - List get currentHand => currentPlayer?.hand?.where((c) => !selectedCards.contains(c))?.toList() ?? []; + List get currentHand => currentPlayer?.hand?.where((c) => !selectedCards.contains(c))?.toList() ?? []; - bool get areWeJudge => currentJudge?.id == userId; + bool get areWeJudge => currentJudge?.id == userId; - bool get haveWeSubmittedResponse => game.turn?.responses?.keys?.contains(userId) ?? false; + bool get haveWeSubmittedResponse => game.turn?.responses?.keys?.contains(userId) ?? false; - bool get allResponsesSubmitted { - if (game.turn != null && game.turn.responses != null && players != null && players.isNotEmpty) { - var allPlayersExcludingJudge = players.where((element) => element.id != game.turn.judgeId).toList(); - for (var value in allPlayersExcludingJudge) { - if (game.turn.responses.keys.firstWhere((element) => element == value.id, orElse: () => null) == null) { - return false; - } - } - return true; - } else { - return false; + bool get allResponsesSubmitted { + if (game.turn != null && game.turn.responses != null && players != null && players.isNotEmpty) { + var allPlayersExcludingJudgeAndInactive = + players.where((element) => element.id != game.turn.judgeId && element.isInactive != true).toList(); + for (var value in allPlayersExcludingJudgeAndInactive) { + if (game.turn.responses.keys.firstWhere((element) => element == value.id, orElse: () => null) == null) { + return false; } + } + return true; + } else { + return false; } - - bool get selectCardsMeetPromptRequirement { - PromptSpecial special = promptSpecial(game.turn?.promptCard?.special); - if (special != null) { - if (special == PromptSpecial.pick2) { - return selectedCards.length == 2; - } else if (special == PromptSpecial.draw2pick3) { - return selectedCards.length == 3; - } - } else { - return selectedCards.isNotEmpty; - } - return false; + } + + bool get selectCardsMeetPromptRequirement { + PromptSpecial special = promptSpecial(game.turn?.promptCard?.special); + if (special != null) { + if (special == PromptSpecial.pick2) { + return selectedCards.length == 2; + } else if (special == PromptSpecial.draw2pick3) { + return selectedCards.length == 3; + } + } else { + return selectedCards.isNotEmpty; } + return false; + } - GameViewState({ - this.userId, - this.game, - this.players, - List selectedCards, - this.isSubmitting = false, - this.isLoading = true, - this.error, - }) : selectedCards = selectedCards ?? []; + GameViewState({ + this.userId, + this.game, + this.players, + List downvotes, + List selectedCards, + this.isSubmitting = false, + this.isLoading = true, + this.error, + }) : selectedCards = selectedCards ?? [], + downvotes = downvotes; - GameViewState copyWith({ - String userId, - Game game, - List players, - List selectedCards, - bool isSubmitting, - bool isLoading, - String error, - }) { - return GameViewState( - userId: userId ?? this.userId, - game: game ?? this.game, - players: players ?? this.players, - selectedCards: selectedCards ?? this.selectedCards, - isSubmitting: isSubmitting ?? this.isSubmitting, - isLoading: isLoading ?? this.isLoading, - error: error ?? this.error, - ); - } + GameViewState copyWith({ + String userId, + Game game, + List players, + List selectedCards, + List downvotes, + bool isSubmitting, + bool isLoading, + String error, + }) { + return GameViewState( + userId: userId ?? this.userId, + game: game ?? this.game, + players: players ?? this.players, + downvotes: downvotes ?? this.downvotes, + selectedCards: selectedCards ?? this.selectedCards, + isSubmitting: isSubmitting ?? this.isSubmitting, + isLoading: isLoading ?? this.isLoading, + error: error ?? this.error, + ); + } - @override - String toString() { - return '''GameViewState { + @override + String toString() { + return '''GameViewState { userId: $userId, game: ${game?.gid}, players: $players, + downvotes: $downvotes, selectedCards: $selectedCards, isSubmitting: $isSubmitting, isLoading: $isLoading, error: $error, isOurGame: $isOurGame, }'''; - } + } } diff --git a/lib/ui/game/screens/gameplay/game_play_screen.dart b/lib/ui/game/screens/gameplay/game_play_screen.dart index 5816186..775fce3 100644 --- a/lib/ui/game/screens/gameplay/game_play_screen.dart +++ b/lib/ui/game/screens/gameplay/game_play_screen.dart @@ -1,5 +1,3 @@ -import 'package:appsagainsthumanity/data/features/cards/model/response_card.dart'; -import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/internal.dart'; import 'package:appsagainsthumanity/ui/game/bloc/bloc.dart'; import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/game_bottom_sheet.dart'; @@ -9,7 +7,6 @@ import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/player_list. import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/player_response_picker.dart'; import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/prompt_container.dart'; import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/re_deal_button.dart'; -import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/response_card_view.dart'; import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/turn_winner_sheet.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart b/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart index f196402..3476e20 100644 --- a/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart +++ b/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart @@ -12,7 +12,7 @@ class JudgeBar extends StatelessWidget { return BlocBuilder( builder: (context, state) { var judge = state.currentJudge; - var hasDownvoted = state.game.turn?.downvotes?.contains(state.userId) ?? false; + var hasDownvoted = state.downvotes?.contains(state.userId) ?? false; if (judge != null) { return _buildHeader( context, diff --git a/lib/ui/game/screens/gameplay/widget/judge/judge_dredd.dart b/lib/ui/game/screens/gameplay/widget/judge/judge_dredd.dart index 4849c80..694aa70 100644 --- a/lib/ui/game/screens/gameplay/widget/judge/judge_dredd.dart +++ b/lib/ui/game/screens/gameplay/widget/judge/judge_dredd.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/internal.dart'; import 'package:appsagainsthumanity/ui/game/bloc/bloc.dart'; import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/judge/judging_pager.dart'; diff --git a/lib/ui/game/screens/gameplay/widget/player_list.dart b/lib/ui/game/screens/gameplay/widget/player_list.dart index 0753d4c..87f9d7a 100644 --- a/lib/ui/game/screens/gameplay/widget/player_list.dart +++ b/lib/ui/game/screens/gameplay/widget/player_list.dart @@ -15,14 +15,14 @@ class PlayerList extends StatelessWidget { create: (context) => GameBloc(null, context.repository())..add(Subscribe(initialGame.id)), child: BlocBuilder( builder: (context, state) { - var players = state.players ?? []; + var players = (state.players ?? []).where((element) => element.isInactive != true).toList(); return ListView.builder( padding: const EdgeInsets.symmetric(vertical: 8), itemCount: players.length, itemBuilder: (context, index) { var player = players[index]; var isJudge = player.id == state.game.turn?.judgeId; - var hasDownvoted = state.game.turn?.downvotes?.contains(player.id) ?? false; + var hasDownvoted = state.downvotes?.contains(player.id) ?? false; return PlayerItem( player, isJudge: isJudge, diff --git a/lib/ui/game/screens/gameplay/widget/prompt_container.dart b/lib/ui/game/screens/gameplay/widget/prompt_container.dart index 89b6ce0..d599391 100644 --- a/lib/ui/game/screens/gameplay/widget/prompt_container.dart +++ b/lib/ui/game/screens/gameplay/widget/prompt_container.dart @@ -2,7 +2,6 @@ import 'package:appsagainsthumanity/data/features/cards/model/prompt_card.dart'; import 'package:appsagainsthumanity/ui/game/bloc/bloc.dart'; import 'package:appsagainsthumanity/internal.dart'; import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/judge/judge_dredd.dart'; -import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/judge/judging_pager.dart'; import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/response_card_view.dart'; import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/waiting_player_responses.dart'; import 'package:flutter/material.dart'; diff --git a/lib/ui/game/screens/gameplay/widget/turn_winner_sheet.dart b/lib/ui/game/screens/gameplay/widget/turn_winner_sheet.dart index 8f9c878..de5542b 100644 --- a/lib/ui/game/screens/gameplay/widget/turn_winner_sheet.dart +++ b/lib/ui/game/screens/gameplay/widget/turn_winner_sheet.dart @@ -1,7 +1,6 @@ import 'package:appsagainsthumanity/data/features/game/model/turn_winner.dart'; import 'package:appsagainsthumanity/internal.dart'; import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/response_card_view.dart'; -import 'package:appsagainsthumanity/ui/widgets/player_circle_avatar.dart'; import 'package:flutter/material.dart'; class TurnWinnerSheet extends StatelessWidget { diff --git a/lib/ui/game/screens/gameplay/widget/waiting_player_responses.dart b/lib/ui/game/screens/gameplay/widget/waiting_player_responses.dart index eb33218..0f5d5a3 100644 --- a/lib/ui/game/screens/gameplay/widget/waiting_player_responses.dart +++ b/lib/ui/game/screens/gameplay/widget/waiting_player_responses.dart @@ -12,7 +12,10 @@ class WaitingPlayerResponses extends StatelessWidget { @override Widget build(BuildContext context) { - var players = state.players?.where((element) => element.id != state.game.turn?.judgeId)?.toList() ?? []; + var players = state.players?.where((element) { + return element.id != state.game.turn?.judgeId && element.isInactive != true; + })?.toList() ?? []; + return Container( margin: const EdgeInsets.symmetric(horizontal: 16), child: Column( diff --git a/lib/ui/game/screens/waiting/waiting_room_screen.dart b/lib/ui/game/screens/waiting/waiting_room_screen.dart index d2747c9..4bf1bd5 100644 --- a/lib/ui/game/screens/waiting/waiting_room_screen.dart +++ b/lib/ui/game/screens/waiting/waiting_room_screen.dart @@ -2,7 +2,6 @@ import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/data/features/game/model/player.dart'; import 'package:appsagainsthumanity/internal.dart'; import 'package:appsagainsthumanity/ui/game/bloc/bloc.dart'; -import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; diff --git a/regenerate.sh b/regenerate.sh new file mode 100755 index 0000000..2615866 --- /dev/null +++ b/regenerate.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +flutter generate +flutter packages pub run build_runner build --delete-conflicting-outputs + +echo "Code Regenerated!" diff --git a/test/widget_test.dart b/test/widget_test.dart index 885f18b..553e64a 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -5,7 +5,6 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:appsagainsthumanity/main.dart'; +import 'package:flutter/material.dart'; // ignore: unused_import +import 'package:flutter_test/flutter_test.dart'; // ignore: unused_import +import 'package:appsagainsthumanity/main.dart'; // ignore: unused_import From 55674f12b2b6f2f9e70153cce4ef8b778bff48c3 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Fri, 24 Apr 2020 16:22:21 -0400 Subject: [PATCH 06/67] Finished handling push notification clicks. --- ios/Flutter/.last_build_id | 2 +- ios/Runner.xcodeproj/project.pbxproj | 3 + lib/app.dart | 30 +- .../game/firestore_game_repository.dart | 24 +- lib/data/features/game/game_repository.dart | 3 + lib/data/features/game/model/player.dart | 2 +- lib/data/features/game/model/turn_winner.dart | 5 + lib/data/features/users/user_repository.dart | 264 +++++++++--------- lib/internal.dart | 8 +- lib/internal/push.dart | 166 ++++++++--- lib/ui/creategame/create_game_screen.dart | 5 +- lib/ui/game/bloc/game_state.dart | 5 +- .../complete/completed_game_screen.dart | 2 +- .../screens/gameplay/game_play_screen.dart | 5 + .../gameplay/widget/judge/judge_bar.dart | 6 +- .../screens/gameplay/widget/player_item.dart | 8 +- .../gameplay/widget/turn_winner_sheet.dart | 30 +- .../screens/waiting/waiting_room_screen.dart | 19 +- lib/ui/home/home_screen.dart | 15 +- lib/ui/routes.dart | 61 +++- lib/ui/signin/sign_in_screen.dart | 8 +- lib/ui/widgets/player_circle_avatar.dart | 22 +- 22 files changed, 456 insertions(+), 237 deletions(-) diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id index a3d20ae..06c5caa 100644 --- a/ios/Flutter/.last_build_id +++ b/ios/Flutter/.last_build_id @@ -1 +1 @@ -2846662d6d846333266c8c97fe0ab973 \ No newline at end of file +5f8e23ba106f05148b26d487766e9033 \ No newline at end of file diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index cb0f099..32e4980 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -410,6 +410,7 @@ SUPPORTS_MACCATALYST = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; @@ -553,6 +554,7 @@ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -586,6 +588,7 @@ SUPPORTS_MACCATALYST = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; diff --git a/lib/app.dart b/lib/app.dart index 59b24ae..c08670a 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,4 +1,5 @@ import 'package:appsagainsthumanity/internal.dart'; +import 'package:appsagainsthumanity/internal/push.dart'; import 'package:appsagainsthumanity/ui/home/home_screen.dart'; import 'package:appsagainsthumanity/ui/routes.dart'; import 'package:appsagainsthumanity/ui/signin/sign_in_screen.dart'; @@ -28,20 +29,23 @@ class App extends StatelessWidget { ], navigatorObservers: [ FirebaseAnalyticsObserver(analytics: Analytics()), - Routes.routeObserver + Routes.routeObserver, + Routes.routeTracer ], - home: BlocBuilder( - builder: (context, state) { - if (state is Unauthenticated) { - return SignInScreen(); - } else if (state is NeedsAgreeToTerms) { - return TermsOfServiceScreen(); - } else if (state is Authenticated) { - return HomeScreen(); - } else { - return Container(color: AppColors.surface); - } - }, + home: PushNavigator( + child: BlocBuilder( + builder: (context, state) { + if (state is Unauthenticated) { + return SignInScreen(); + } else if (state is NeedsAgreeToTerms) { + return TermsOfServiceScreen(); + } else if (state is Authenticated) { + return HomeScreen(); + } else { + return Container(color: AppColors.surface); + } + }, + ), ), ); } diff --git a/lib/data/features/game/firestore_game_repository.dart b/lib/data/features/game/firestore_game_repository.dart index 6fbc9e0..54ab264 100644 --- a/lib/data/features/game/firestore_game_repository.dart +++ b/lib/data/features/game/firestore_game_repository.dart @@ -47,7 +47,10 @@ class FirestoreGameRepository extends GameRepository { Future joinGame(String gid) { return currentUserOrThrow((firebaseUser) async { var snapshot = - await db.collection(FirebaseConstants.COLLECTION_GAMES).where('gid', isEqualTo: gid).limit(1).getDocuments(); + await db.collection(FirebaseConstants.COLLECTION_GAMES) + .where('gid', isEqualTo: gid) + .limit(1) + .getDocuments(); if (snapshot != null && snapshot.documents.isNotEmpty) { var gameDocument = snapshot.documents.first; @@ -82,6 +85,15 @@ class FirestoreGameRepository extends GameRepository { } } + @override + Future getGame(String gameDocumentId) async { + var gameDocument = db.collection(FirebaseConstants.COLLECTION_GAMES) + .document(gameDocumentId); + + var snapshot = await gameDocument.get(); + return Game.fromDocument(snapshot); + } + @override Stream> observeJoinedGames() { return streamCurrentUserOrThrow((firebaseUser) { @@ -120,7 +132,13 @@ class FirestoreGameRepository extends GameRepository { .document(FirebaseConstants.DOCUMENT_TALLY); return collection.snapshots() - .map((snapshot) => List.from(snapshot['votes'] ?? [])); + .map((snapshot) { + if (snapshot.data != null) { + return List.from(snapshot.data['votes'] ?? []); + } else { + return []; + } + }); } @override @@ -194,7 +212,7 @@ class FirestoreGameRepository extends GameRepository { .collection(FirebaseConstants.COLLECTION_DOWNVOTES) .document(FirebaseConstants.DOCUMENT_TALLY); - await gameDocument.updateData({ + await gameDocument.setData({ 'votes': FieldValue.arrayUnion([firebaseUser.uid]) }); }); diff --git a/lib/data/features/game/game_repository.dart b/lib/data/features/game/game_repository.dart index e59ae37..4f7084a 100644 --- a/lib/data/features/game/game_repository.dart +++ b/lib/data/features/game/game_repository.dart @@ -17,6 +17,9 @@ abstract class GameRepository { /// Find an existing game using the [gid] game id code Future findGame(String gid); + /// Get a game by it's actual document id + Future getGame(String gameDocumentId); + /// Return a list of games that you have joined in the past Stream> observeJoinedGames(); diff --git a/lib/data/features/game/model/player.dart b/lib/data/features/game/model/player.dart index 3c4c73a..9926eaf 100644 --- a/lib/data/features/game/model/player.dart +++ b/lib/data/features/game/model/player.dart @@ -9,7 +9,7 @@ part 'player.g.dart'; @immutable @JsonSerializable() class Player { - static const DEFAULT_NAME = "John \"I need a name\" Smith"; + static const DEFAULT_NAME = "\"A man needs a name\""; final String id; final String name; diff --git a/lib/data/features/game/model/turn_winner.dart b/lib/data/features/game/model/turn_winner.dart index 5522f51..3d76cbc 100644 --- a/lib/data/features/game/model/turn_winner.dart +++ b/lib/data/features/game/model/turn_winner.dart @@ -32,6 +32,11 @@ class TurnWinner extends Equatable { Map toJson() => _$TurnWinnerToJson(this); + @override + String toString() { + return 'TurnWinner{playerId: $playerId, playerName: $playerName, playerAvatarUrl: $playerAvatarUrl, isRandoCardrissian: $isRandoCardrissian, promptCard: $promptCard, response: $response}'; + } + @override List get props => [playerId, playerName, playerAvatarUrl, isRandoCardrissian, promptCard, response]; } diff --git a/lib/data/features/users/user_repository.dart b/lib/data/features/users/user_repository.dart index adf45b9..7751f38 100644 --- a/lib/data/features/users/user_repository.dart +++ b/lib/data/features/users/user_repository.dart @@ -10,147 +10,143 @@ import 'package:google_sign_in/google_sign_in.dart'; import 'package:logging/logging.dart'; class UserRepository { - final FirebaseAuth _auth; - final Firestore _db = Firestore.instance; - - UserRepository({FirebaseAuth firebaseAuth}) - : _auth = firebaseAuth ?? FirebaseAuth.instance; - - Future isSignedIn() async { - return (await _auth.currentUser()) != null; - } - - Future getUser() { - return currentUserOrThrow((firebaseUser) async { - var doc = _userDocument(firebaseUser); - var snapshot = await doc.get(); - return User( - id: doc.documentID, - name: snapshot['name'], - avatarUrl: snapshot['avatarUrl'], - updatedAt: (snapshot['updatedAt'] as Timestamp).toDate() - ); - }); + final FirebaseAuth _auth; + final Firestore _db = Firestore.instance; + + UserRepository({FirebaseAuth firebaseAuth}) : _auth = firebaseAuth ?? FirebaseAuth.instance; + + Future isSignedIn() async { + return (await _auth.currentUser()) != null; + } + + Future getUser() { + return currentUserOrThrow((firebaseUser) async { + var doc = _userDocument(firebaseUser); + var snapshot = await doc.get(); + return User( + id: doc.documentID, + name: snapshot['name'], + avatarUrl: snapshot['avatarUrl'], + updatedAt: (snapshot['updatedAt'] as Timestamp).toDate()); + }); + } + + Future signInWithGoogle() async { + GoogleSignIn _googleSignIn = GoogleSignIn( + scopes: [ + 'email', + 'https://www.googleapis.com/auth/userinfo.profile', + ], + ); + + try { + var acct = await _googleSignIn.signIn(); + var auth = await acct.authentication; + + var result = await _auth + .signInWithCredential(GoogleAuthProvider.getCredential(idToken: auth.idToken, accessToken: auth.accessToken)); + + return _finishSigningInWithResult(result); + } catch (error) { + print(error); + throw error; } - - Future signInWithGoogle() async { - GoogleSignIn _googleSignIn = GoogleSignIn( - scopes: [ - 'email', - 'https://www.googleapis.com/auth/userinfo.profile', - ], + } + + Future signInWithApple() async { + final AuthorizationResult result = await AppleSignIn.performRequests([ + AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName]) + ]); + + switch (result.status) { + case AuthorizationStatus.authorized: + // Success + final AppleIdCredential appleIdCredential = result.credential; + OAuthProvider oAuthProvider = OAuthProvider(providerId: "apple.com"); + final AuthCredential credential = oAuthProvider.getCredential( + idToken: String.fromCharCodes(appleIdCredential.identityToken), + accessToken: String.fromCharCodes(appleIdCredential.authorizationCode), ); - try { - var acct = await _googleSignIn.signIn(); - var auth = await acct.authentication; - - var result = await _auth.signInWithCredential(GoogleAuthProvider.getCredential( - idToken: auth.idToken, - accessToken: auth.accessToken - )); - - return _finishSigningInWithResult(result); - } catch (error) { - print(error); - throw error; + var name = Player.DEFAULT_NAME; + if (appleIdCredential.fullName != null) { + print("Apple Name(nick=${appleIdCredential.fullName.nickname}, " + "given=${appleIdCredential.fullName.givenName}, " + "middle=${appleIdCredential.fullName.middleName}, " + "family=${appleIdCredential.fullName.familyName})"); + var nameComponents = appleIdCredential.fullName; + name = nameComponents.nickname ?? + "${nameComponents.givenName ?? nameComponents.middleName ?? ""} ${nameComponents.familyName ?? ""}"; } - } - Future signInWithApple() async { - final AuthorizationResult result = await AppleSignIn.performRequests([ - AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName]) - ]); - - switch (result.status) { - case AuthorizationStatus.authorized: - // Success - final AppleIdCredential appleIdCredential = result.credential; - OAuthProvider oAuthProvider = OAuthProvider(providerId: "apple.com"); - final AuthCredential credential = oAuthProvider.getCredential( - idToken: String.fromCharCodes(appleIdCredential.identityToken), - accessToken: String.fromCharCodes(appleIdCredential.authorizationCode), - ); - - var name = Player.DEFAULT_NAME; - if (appleIdCredential.fullName != null) { - var nameComponents = appleIdCredential.fullName; - name = nameComponents.nickname ?? - "${nameComponents.givenName ?? nameComponents.middleName ?? ""} ${nameComponents.familyName ?? ""}"; - } - - final AuthResult _result = await _auth.signInWithCredential(credential); - - return _finishSigningInWithResult(_result, name: name); - break; - case AuthorizationStatus.error: - throw result.error.localizedFailureReason; - break; - case AuthorizationStatus.cancelled: - print("User Cancelled operation"); - throw 'Cancelled'; - break; - default: - throw 'Unable to sign in'; - } + final AuthResult _result = await _auth.signInWithCredential(credential); + + return _finishSigningInWithResult(_result, name: name); + break; + case AuthorizationStatus.error: + throw result.error.localizedFailureReason; + break; + case AuthorizationStatus.cancelled: + print("User Cancelled operation"); + throw 'Cancelled'; + break; + default: + throw 'Unable to sign in'; } - - Future _finishSigningInWithResult(AuthResult result, {String name}) async { - try { - if (result.user != null) { - if (name != null && result.user.displayName != name) { - var updateInfo = UserUpdateInfo(); - updateInfo.displayName = name; - await result.user.updateProfile(updateInfo); - await result.user.reload(); - print("Updated user's name to $name"); - } - - // Create the user obj acct in FB - var userDoc = _userDocument(result.user); - - await userDoc.setData({ - "name": result.user.displayName, - "avatarUrl": result.user.photoUrl, - "updatedAt": Timestamp.now() - }, merge: true); - - Logger("UserRepository").fine( - "Signed-in! User(id=${result.user.uid}, name=${result.user.displayName}, photoUrl=${result.user - .photoUrl})"); - - // Excellent! Let's create our device - await PushNotifications().checkAndUpdateToken(); - - return User( - id: result.user.uid, - name: result.user.displayName, - avatarUrl: result.user.photoUrl, - updatedAt: DateTime.now() - ); - } else { - throw result; - } - } catch (error) { - print(error); - throw error; + } + + Future _finishSigningInWithResult(AuthResult result, {String name}) async { + try { + if (result.user != null) { + if (name != null && result.user.displayName != name) { + var updateInfo = UserUpdateInfo(); + updateInfo.displayName = name; + updateInfo.photoUrl = result.user.photoUrl; + await result.user.updateProfile(updateInfo); + await result.user.reload(); + print("Updated user's name to $name"); } - } - - /// Sign-out of the user's account - Future signOut() async { - await AppPreferences().clear(); - await _auth.signOut(); - } - - Future deleteAccount() async { - await AppPreferences().clear(); - var user = await _auth.currentUser(); - await user.delete(); - } - DocumentReference _userDocument(FirebaseUser user) { - return _db.collection(FirebaseConstants.COLLECTION_USERS) - .document(user.uid); + // Create the user obj acct in FB + var userDoc = _userDocument(result.user); + + await userDoc.setData( + {"name": result.user.displayName, "avatarUrl": result.user.photoUrl, "updatedAt": Timestamp.now()}, + merge: true); + + Logger("UserRepository").fine( + "Signed-in! User(id=${result.user.uid}, name=${result.user.displayName}, photoUrl=${result.user.photoUrl})"); + + // Excellent! Let's create our device + await PushNotifications().checkAndUpdateToken(force: true); + + return User( + id: result.user.uid, + name: result.user.displayName, + avatarUrl: result.user.photoUrl, + updatedAt: DateTime.now()); + } else { + throw result; + } + } catch (error) { + print(error); + throw error; } + } + + /// Sign-out of the user's account + Future signOut() async { + await AppPreferences().clear(); + await _auth.signOut(); + } + + Future deleteAccount() async { + await AppPreferences().clear(); + var user = await _auth.currentUser(); + await user.delete(); + } + + DocumentReference _userDocument(FirebaseUser user) { + return _db.collection(FirebaseConstants.COLLECTION_USERS).document(user.uid); + } } diff --git a/lib/internal.dart b/lib/internal.dart index ed198c3..6873219 100644 --- a/lib/internal.dart +++ b/lib/internal.dart @@ -1,4 +1,4 @@ -export 'internal/localization.dart'; -export 'internal/theme.dart'; -export 'internal/analytics.dart'; -export 'config.dart'; +export 'package:appsagainsthumanity/internal/localization.dart'; +export 'package:appsagainsthumanity/internal/theme.dart'; +export 'package:appsagainsthumanity/internal/analytics.dart'; +export 'package:appsagainsthumanity/config.dart'; diff --git a/lib/internal/push.dart b/lib/internal/push.dart index f77e2e0..43fc291 100644 --- a/lib/internal/push.dart +++ b/lib/internal/push.dart @@ -1,49 +1,149 @@ import 'dart:convert'; +import 'dart:io'; import 'package:appsagainsthumanity/data/app_preferences.dart'; import 'package:appsagainsthumanity/data/features/devices/device_repository.dart'; +import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; +import 'package:appsagainsthumanity/data/features/game/model/turn.dart'; +import 'package:appsagainsthumanity/ui/routes.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:logging/logging.dart'; class PushNotifications { - PushNotifications._(); + PushNotifications._(); - static PushNotifications _instance = PushNotifications._(); + static PushNotifications _instance = PushNotifications._(); + final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); - factory PushNotifications() { - return _instance; - } + factory PushNotifications() { + return _instance; + } + + void checkPermissions() { + _firebaseMessaging.requestNotificationPermissions(); + } + + void setup() { + // Setup firebase messaging + _firebaseMessaging.onTokenRefresh.listen((token) { + DeviceRepository().updatePushToken(token); + }); + _firebaseMessaging.configure(onLaunch: (Map message) async { + print("onLaunch()"); + print(JsonEncoder().convert(message)); + }, onMessage: (Map message) async { + print("onMessage()"); + print(JsonEncoder().convert(message)); + }, onResume: (Map message) async { + print("onResume()"); + print(JsonEncoder().convert(message)); + }); + + checkAndUpdateToken(); + } - void setup() { - // Setup firebase messaging - final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); - _firebaseMessaging.onTokenRefresh.listen((token) { - DeviceRepository().updatePushToken(token); - }); - _firebaseMessaging.configure( - onLaunch: (Map message) async { - print("onLaunch()"); - print(JsonEncoder().convert(message)); - }, - onMessage: (Map message) async { - print("onMessage()"); - print(JsonEncoder().convert(message)); - }, - onResume: (Map message) async { - print("onResume()"); - print(JsonEncoder().convert(message)); - } - ); - - checkAndUpdateToken(); + Future checkAndUpdateToken({bool force = false}) async { + final FirebaseMessaging messaging = FirebaseMessaging(); + String token = await messaging.getToken(); + if (token != AppPreferences().pushToken || force) { + Logger.root.fine("FCM Token is different from what is stored in preferences, updating device..."); + DeviceRepository().updatePushToken(token); } + } +} + +class PushNavigator extends StatefulWidget { + final Widget child; - Future checkAndUpdateToken() async { - final FirebaseMessaging messaging = FirebaseMessaging(); - String token = await messaging.getToken(); - if (token != AppPreferences().pushToken) { - Logger.root.fine("FCM Token is different from what is stored in preferences, updating device..."); - DeviceRepository().updatePushToken(token); + PushNavigator({@required this.child}); + + @override + _PushNavigatorState createState() => _PushNavigatorState(); +} + +class _PushNavigatorState extends State { + final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); + GameRepository _gameRepository; + + @override + void initState() { + super.initState(); + _gameRepository = context.repository(); + _firebaseMessaging.configure( + onLaunch: (Map message) async { + print("onLaunch()"); + print(JsonEncoder().convert(message)); + handleNotificationClick(message); + }, + onMessage: (Map message) async { + // This is called when message comes while in foreground + print("onMessage()"); + // Do nothing + }, + onResume: (Map message) async { + // This is called when user is + print("onResume()"); + print(JsonEncoder().convert(message)); + handleNotificationClick(message); + }, + ); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } + + void handleNotificationClick(Map message) async { + var gameId = Platform.isAndroid ? message['data']['gameId'] : message['gameId']; + if (gameId != null && gameId is String && gameId.isNotEmpty) { + var currentRoute = Routes.routeTracer.currentRoute; + print(currentRoute); + if (currentRoute.settings.arguments == gameId) { + // It looks like the current game is up fromt + print("Game is already in the foreground"); + return; + } + try { + var game = await _gameRepository.getGame(gameId); + if (game != null) { + /* + * Neuter the turn winner out of the otherwise the winner bottom sheet will never show + */ + if (game.turn != null) { + game = game.copyWith( + turn: Turn( + judgeId: game.turn.judgeId, + responses: game.turn.responses, + promptCard: game.turn.promptCard, + winner: null + ) + ); + } + + if (currentRoute.settings.name == Routes.game) { + print("Replacing current shown game"); + currentRoute.navigator + .pushReplacement(GamePageRoute(game)); + } else if (currentRoute.settings.name == "/"){ + print("User should be in the homescreen, push game"); + currentRoute.navigator + .push(GamePageRoute(game)); + } else if (currentRoute is MaterialPageRoute){ + print("User is not in a game or homescreen"); + currentRoute.navigator + ..popUntil((route) => route.settings.name == "/") + ..push(GamePageRoute(game)); + } } + } catch (e) { + print(e); + } } + } + + } diff --git a/lib/ui/creategame/create_game_screen.dart b/lib/ui/creategame/create_game_screen.dart index 9a6dd1a..c3e0ac2 100644 --- a/lib/ui/creategame/create_game_screen.dart +++ b/lib/ui/creategame/create_game_screen.dart @@ -2,6 +2,7 @@ import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/ui/creategame/bloc/bloc.dart'; import 'package:appsagainsthumanity/internal.dart'; import 'package:appsagainsthumanity/ui/game/game_screen.dart'; +import 'package:appsagainsthumanity/ui/routes.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kt_dart/kt.dart'; @@ -83,8 +84,8 @@ class _CreateGameScreenState extends State { ? () async { // Start game? var newGame = await context.repository().createGame(state.selectedSets); - - Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => GameScreen(newGame))); + Navigator.of(context) + .pushReplacement(GamePageRoute(newGame)); } : null, ), diff --git a/lib/ui/game/bloc/game_state.dart b/lib/ui/game/bloc/game_state.dart index ac7b892..dc8de2c 100644 --- a/lib/ui/game/bloc/game_state.dart +++ b/lib/ui/game/bloc/game_state.dart @@ -113,8 +113,9 @@ class GameViewState { String toString() { return '''GameViewState { userId: $userId, - game: ${game?.gid}, - players: $players, + game: ${game?.gid}, + turn: ${game?.turn?.winner}, + players: ${players?.length}, downvotes: $downvotes, selectedCards: $selectedCards, isSubmitting: $isSubmitting, diff --git a/lib/ui/game/screens/complete/completed_game_screen.dart b/lib/ui/game/screens/complete/completed_game_screen.dart index 5b3a2dd..faeab25 100644 --- a/lib/ui/game/screens/complete/completed_game_screen.dart +++ b/lib/ui/game/screens/complete/completed_game_screen.dart @@ -56,7 +56,7 @@ class CompletedGameScreen extends StatelessWidget { Container( margin: const EdgeInsets.only(top: 24), child: Text( - state.winner?.name, + state.winner?.name ?? Player.DEFAULT_NAME, style: context.theme.textTheme.headline4.copyWith( color: Colors.white, ), diff --git a/lib/ui/game/screens/gameplay/game_play_screen.dart b/lib/ui/game/screens/gameplay/game_play_screen.dart index 775fce3..8bb269b 100644 --- a/lib/ui/game/screens/gameplay/game_play_screen.dart +++ b/lib/ui/game/screens/gameplay/game_play_screen.dart @@ -75,12 +75,17 @@ class _GamePlayScreenState extends State { ), body: BlocListener( condition: (previous, current) { + print("Winner Check:"); + print(" current=${current.game.turn?.winner}"); + print(" previous=${previous.game.turn?.winner}"); + print(" hasChange=${current.game.turn?.winner != previous.game.turn?.winner}"); return current.game.turn?.winner != previous.game.turn?.winner; }, listener: (context, state) { // Show bottom sheet modal for the winner var turnWinner = state.game.turn?.winner; if (turnWinner != null) { + print("Showing winner sheet"); showModalBottomSheet( context: context, backgroundColor: Colors.transparent, diff --git a/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart b/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart index 3476e20..8abd636 100644 --- a/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart +++ b/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart @@ -27,9 +27,13 @@ class JudgeBar extends StatelessWidget { } Widget _buildHeader(BuildContext context, Player player, {bool hasDownvoted = false}) { + var playerName = player.name ?? Player.DEFAULT_NAME; + if (playerName.trim().isEmpty) { + playerName = Player.DEFAULT_NAME; + } return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 12), - title: Text(player.name), + title: Text(playerName), subtitle: Text("Current judge", style: context.theme.textTheme.bodyText2.copyWith(color: Colors.white60)), leading: _buildJudgeAvatar(context, player), trailing: IconButton( diff --git a/lib/ui/game/screens/gameplay/widget/player_item.dart b/lib/ui/game/screens/gameplay/widget/player_item.dart index 7f3fedf..5cc3968 100644 --- a/lib/ui/game/screens/gameplay/widget/player_item.dart +++ b/lib/ui/game/screens/gameplay/widget/player_item.dart @@ -13,11 +13,15 @@ class PlayerItem extends StatelessWidget { @override Widget build(BuildContext context) { + var playerName = player.name ?? Player.DEFAULT_NAME; + if (playerName.trim().isEmpty) { + playerName = Player.DEFAULT_NAME; + } return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 4), onTap: () {}, title: Text( - player.name ?? Player.DEFAULT_NAME, + playerName, style: context.theme.textTheme.subtitle1.copyWith(color: Colors.white), ), subtitle: isJudge @@ -35,7 +39,7 @@ class PlayerItem extends StatelessWidget { children: [ if (hasDownvoted) Container( - margin: const EdgeInsets.only(right: 16), + margin: const EdgeInsets.only(right: 16), child: Icon( MdiIcons.thumbDown, color: AppColors.secondary, diff --git a/lib/ui/game/screens/gameplay/widget/turn_winner_sheet.dart b/lib/ui/game/screens/gameplay/widget/turn_winner_sheet.dart index de5542b..3087eff 100644 --- a/lib/ui/game/screens/gameplay/widget/turn_winner_sheet.dart +++ b/lib/ui/game/screens/gameplay/widget/turn_winner_sheet.dart @@ -1,6 +1,8 @@ +import 'package:appsagainsthumanity/data/features/game/model/player.dart'; import 'package:appsagainsthumanity/data/features/game/model/turn_winner.dart'; import 'package:appsagainsthumanity/internal.dart'; import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/response_card_view.dart'; +import 'package:appsagainsthumanity/ui/widgets/player_circle_avatar.dart'; import 'package:flutter/material.dart'; class TurnWinnerSheet extends StatelessWidget { @@ -10,6 +12,10 @@ class TurnWinnerSheet extends StatelessWidget { @override Widget build(BuildContext context) { + var playerName = turnWinner.playerName ?? Player.DEFAULT_NAME; + if (playerName.trim().isEmpty) { + playerName = Player.DEFAULT_NAME; + } return SizedBox.expand( child: SingleChildScrollView( child: Column( @@ -31,7 +37,7 @@ class TurnWinnerSheet extends StatelessWidget { Container( margin: const EdgeInsets.only(top: 16), child: Text( - turnWinner.playerName, + playerName, style: context.theme.textTheme.headline5.copyWith( color: Colors.white, ), @@ -49,18 +55,14 @@ class TurnWinnerSheet extends StatelessWidget { } Widget _buildAvatar(BuildContext context) { - return turnWinner.isRandoCardrissian - ? CircleAvatar( - backgroundImage: AssetImage("assets/rando_cardrissian.png"), - radius: 40, - ) - : CircleAvatar( - radius: 40, - backgroundImage: turnWinner.playerAvatarUrl != null ? NetworkImage(turnWinner.playerAvatarUrl) : null, - backgroundColor: AppColors.primary, - child: turnWinner.playerAvatarUrl == null - ? Text(turnWinner.playerName.split(' ').map((e) => e[0]).join().toUpperCase()) - : null, - ); + return PlayerCircleAvatar( + radius: 40, + player: Player( + id: turnWinner.playerId, + name: turnWinner.playerName, + avatarUrl: turnWinner.playerAvatarUrl, + isRandoCardrissian: turnWinner.isRandoCardrissian, + ), + ); } } diff --git a/lib/ui/game/screens/waiting/waiting_room_screen.dart b/lib/ui/game/screens/waiting/waiting_room_screen.dart index 4bf1bd5..f33b86f 100644 --- a/lib/ui/game/screens/waiting/waiting_room_screen.dart +++ b/lib/ui/game/screens/waiting/waiting_room_screen.dart @@ -2,6 +2,7 @@ import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/data/features/game/model/player.dart'; import 'package:appsagainsthumanity/internal.dart'; import 'package:appsagainsthumanity/ui/game/bloc/bloc.dart'; +import 'package:appsagainsthumanity/ui/widgets/player_circle_avatar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; @@ -112,23 +113,19 @@ class WaitingRoomScreen extends StatelessWidget { } Widget _buildPlayerTile(BuildContext context, Player player, int index) { + var playerName = player.name ?? Player.DEFAULT_NAME; + if (playerName.trim().isEmpty) { + playerName = Player.DEFAULT_NAME; + } return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 4), onTap: () {}, title: Text( - player.name ?? Player.DEFAULT_NAME, + playerName, style: context.theme.textTheme.subtitle1.copyWith(color: Colors.white), ), - leading: player.isRandoCardrissian - ? CircleAvatar(backgroundImage: AssetImage("assets/rando_cardrissian.png")) - : CircleAvatar( - radius: 20, - backgroundImage: player.avatarUrl != null ? NetworkImage(player.avatarUrl) : null, - backgroundColor: AppColors.primary, - child: player.avatarUrl == null - ? Text(player.name.split(' ').map((e) => e[0]).join().toUpperCase()) - : null, - )); + leading: PlayerCircleAvatar(player: player) + ); } Widget _buildRandoCardrissianInvite(BuildContext context, String gameDocumentId) { diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index 5f7e22e..73d9991 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -4,10 +4,12 @@ import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/data/features/game/model/player.dart'; import 'package:appsagainsthumanity/data/features/users/model/user.dart'; import 'package:appsagainsthumanity/data/features/game/model/game_state.dart'; +import 'package:appsagainsthumanity/internal/push.dart'; import 'package:appsagainsthumanity/ui/creategame/create_game_screen.dart'; import 'package:appsagainsthumanity/ui/game/game_screen.dart'; import 'package:appsagainsthumanity/ui/home/bloc/bloc.dart'; import 'package:appsagainsthumanity/ui/home/widgets/join_room_dialog.dart'; +import 'package:appsagainsthumanity/ui/routes.dart'; import 'package:appsagainsthumanity/ui/settings/settings_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -82,6 +84,7 @@ class _HomeScreenState extends State { icon: MdiIcons.gamepad, label: "START GAME", onTap: () { + PushNotifications().checkPermissions(); Navigator.of(context).push(MaterialPageRoute(builder: (context) => CreateGameScreen())); }, ), @@ -91,7 +94,10 @@ class _HomeScreenState extends State { margin: const EdgeInsets.only(left: 8, top: 16, right: 24, bottom: 16), icon: MdiIcons.gamepadVariantOutline, label: "JOIN GAME", - onTap: () => _joinGame(context), + onTap: () { + PushNotifications().checkPermissions(); + _joinGame(context); + }, ); }), ], @@ -193,7 +199,6 @@ class _HomeScreenState extends State { itemCount: state.games.length, itemBuilder: (context, index) { var game = state.games[index]; - print("Render ${game.gid}"); return ListTile( title: Text( game.gid, @@ -213,9 +218,8 @@ class _HomeScreenState extends State { // Fetch and load game try { var existingGame = await context.repository().findGame(game.gid); - Navigator.of(context) - .push(MaterialPageRoute(builder: (_) => GameScreen(existingGame))); + .push(GamePageRoute(existingGame)); } catch (e) { Scaffold.of(context) ..hideCurrentSnackBar() @@ -334,8 +338,7 @@ class _HomeScreenState extends State { if (gameId != null) { try { var game = await context.repository().joinGame(gameId); - - Navigator.of(context).push(MaterialPageRoute(builder: (_) => GameScreen(game))); + Navigator.of(context).push(GamePageRoute(game)); } catch (e) { Scaffold.of(context) ..hideCurrentSnackBar() diff --git a/lib/ui/routes.dart b/lib/ui/routes.dart index b48ee61..9ca5e44 100644 --- a/lib/ui/routes.dart +++ b/lib/ui/routes.dart @@ -1,10 +1,63 @@ +import 'package:appsagainsthumanity/data/features/game/model/game.dart'; +import 'package:appsagainsthumanity/ui/game/game_screen.dart'; import 'package:flutter/material.dart'; class Routes { - Routes._(); + Routes._(); - static const String signIn = "/sign_in"; - static const String home = "/home"; + static const String signIn = "/sign_in"; + static const String home = "/home"; + static const String game = "/game"; - static RouteObserver routeObserver = RouteObserver(); + static RouteObserver routeObserver = RouteObserver(); + static RouteTracer routeTracer = RouteTracer(); +} + +class GamePageRoute extends MaterialPageRoute { + GamePageRoute(Game game) + : super( + settings: RouteSettings( + name: Routes.game, + arguments: game.id, + ), + builder: (_) => GameScreen(game), + ); +} + +class NoAnimationPageRoute extends MaterialPageRoute { + NoAnimationPageRoute({WidgetBuilder builder}) : super(builder: builder); + + @override + Widget buildTransitions( + BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { + return child; + } +} + +class RouteTracer extends NavigatorObserver { + Route currentRoute; + + @override + void didPop(Route route, Route previousRoute) { + currentRoute = previousRoute; + print("Route Changed => $currentRoute"); + } + + @override + void didReplace({Route newRoute, Route oldRoute}) { + currentRoute = newRoute; + print("Route Changed => $currentRoute"); + } + + @override + void didRemove(Route route, Route previousRoute) { + currentRoute = previousRoute; + print("Route Changed => $currentRoute"); + } + + @override + void didPush(Route route, Route previousRoute) { + currentRoute = route; + print("Route Changed => $currentRoute"); + } } diff --git a/lib/ui/signin/sign_in_screen.dart b/lib/ui/signin/sign_in_screen.dart index 6941a56..07ae237 100644 --- a/lib/ui/signin/sign_in_screen.dart +++ b/lib/ui/signin/sign_in_screen.dart @@ -45,10 +45,10 @@ class _SignInScreen extends State { builder: (context, state) { return Column( children: [ - Container( - margin: const EdgeInsets.only(left: 24, right: 24, top: 48), - child: AspectRatio( - aspectRatio: 312 / 436, + Expanded( + child: Container( + width: double.maxFinite, + margin: const EdgeInsets.only(left: 24, right: 24, top: 48), child: Material( elevation: 4.0, borderRadius: BorderRadius.circular(16), diff --git a/lib/ui/widgets/player_circle_avatar.dart b/lib/ui/widgets/player_circle_avatar.dart index 9d860e2..dfb0c02 100644 --- a/lib/ui/widgets/player_circle_avatar.dart +++ b/lib/ui/widgets/player_circle_avatar.dart @@ -6,6 +6,24 @@ class PlayerCircleAvatar extends StatelessWidget { final Player player; final double radius; + String get playerInitials { + var splitName = player.name.split(' '); + if (splitName != null && splitName.isNotEmpty) { + var nonEmptyCharacters = splitName + .where((e) => e.isNotEmpty); + if (nonEmptyCharacters.isNotEmpty) { + return nonEmptyCharacters + .map((e) => e[0]) + .join() + .toUpperCase(); + } else { + return ""; + } + } else { + return ""; + } + } + PlayerCircleAvatar({@required this.player, this.radius = 20}); @override @@ -20,7 +38,9 @@ class PlayerCircleAvatar extends StatelessWidget { backgroundImage: player.avatarUrl != null ? NetworkImage(player.avatarUrl) : null, backgroundColor: AppColors.primary, child: player.avatarUrl == null - ? player.name != null ? Text(player.name.split(' ').map((e) => e[0]).join().toUpperCase()) : null + ? player.name != null + ? Text(playerInitials) + : null : null, ); } From 4789ab172a8cba4fafdc1cfd64d75de7c3f500bf Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Fri, 24 Apr 2020 16:31:48 -0400 Subject: [PATCH 07/67] =?UTF-8?q?Fixed=20#11=20-=20Player=20list=20can=20n?= =?UTF-8?q?ow=20scroll=20to=20the=20top=20of=20the=20screen=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screens/gameplay/game_play_screen.dart | 38 +++++++++++-------- .../screens/gameplay/widget/player_list.dart | 4 +- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/lib/ui/game/screens/gameplay/game_play_screen.dart b/lib/ui/game/screens/gameplay/game_play_screen.dart index 8bb269b..cd1e9ff 100644 --- a/lib/ui/game/screens/gameplay/game_play_screen.dart +++ b/lib/ui/game/screens/gameplay/game_play_screen.dart @@ -132,23 +132,29 @@ class _GamePlayScreenState extends State { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, + isScrollControlled: true, builder: (context) { - return GameBottomSheet( - title: "Players", - actions: [ - Container( - margin: const EdgeInsets.symmetric(horizontal: 24), - height: 56, - alignment: Alignment.center, - child: Text( - widget.state.game.gid, - style: context.theme.textTheme.headline6.copyWith( - color: AppColors.secondary, - ), - ), - ) - ], - child: PlayerList(widget.state.game), + return DraggableScrollableSheet( + maxChildSize: 0.885, + builder: (context, scrollController) { + return GameBottomSheet( + title: "Players", + actions: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 24), + height: 56, + alignment: Alignment.center, + child: Text( + widget.state.game.gid, + style: context.theme.textTheme.headline6.copyWith( + color: AppColors.secondary, + ), + ), + ) + ], + child: PlayerList(widget.state.game, scrollController), + ); + }, ); }); } diff --git a/lib/ui/game/screens/gameplay/widget/player_list.dart b/lib/ui/game/screens/gameplay/widget/player_list.dart index 87f9d7a..70d4f1a 100644 --- a/lib/ui/game/screens/gameplay/widget/player_list.dart +++ b/lib/ui/game/screens/gameplay/widget/player_list.dart @@ -6,8 +6,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class PlayerList extends StatelessWidget { final Game initialGame; + final ScrollController scrollController; - PlayerList(this.initialGame); + PlayerList(this.initialGame, this.scrollController); @override Widget build(BuildContext context) { @@ -17,6 +18,7 @@ class PlayerList extends StatelessWidget { builder: (context, state) { var players = (state.players ?? []).where((element) => element.isInactive != true).toList(); return ListView.builder( + controller: scrollController, padding: const EdgeInsets.symmetric(vertical: 8), itemCount: players.length, itemBuilder: (context, index) { From 7ff1896ac7dd549d90d5965fe46a4369769caf1c Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Fri, 24 Apr 2020 17:00:23 -0400 Subject: [PATCH 08/67] Fixed #7 - Removed visual duplicates from game id character pool to make them more user friendly --- lib/data/features/game/firestore_game_repository.dart | 7 +++---- lib/data/firestore.dart | 2 ++ lib/ui/home/widgets/join_room_dialog.dart | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/data/features/game/firestore_game_repository.dart b/lib/data/features/game/firestore_game_repository.dart index 54ab264..64229e0 100644 --- a/lib/data/features/game/firestore_game_repository.dart +++ b/lib/data/features/game/firestore_game_repository.dart @@ -25,10 +25,9 @@ class FirestoreGameRepository extends GameRepository { Future createGame(KtSet cardSets, {int prizesToWin = 10}) { return currentUserOrThrow((firebaseUser) async { var newGameDoc = db.collection(FirebaseConstants.COLLECTION_GAMES).document(); - var game = Game( id: newGameDoc.documentID, - gid: generateId(), + gid: generateId(length: FirebaseConstants.MAX_GID_SIZE), ownerId: firebaseUser.uid, state: GameState.waitingRoom, prizesToWin: prizesToWin, @@ -271,8 +270,8 @@ class FirestoreGameRepository extends GameRepository { .setData(userGame.toJson()); } - String generateId({int length = 5}) { - String source = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + String generateId({int length = 7}) { + String source = "ACEFHJKLMNPQRTUVWXY3479"; StringBuffer builder = StringBuffer(); for (var i = 0; i < length; i++) { builder.write(source[Random().nextInt(source.length)]); diff --git a/lib/data/firestore.dart b/lib/data/firestore.dart index 4b2689f..0fb09be 100644 --- a/lib/data/firestore.dart +++ b/lib/data/firestore.dart @@ -12,6 +12,8 @@ class FirebaseConstants { static const DOCUMENT_RANDO_CARDRISSIAN = "rando-cardrissian"; static const DOCUMENT_TALLY = "tally"; + + static const MAX_GID_SIZE = 5; } class UserNotFoundException { } diff --git a/lib/ui/home/widgets/join_room_dialog.dart b/lib/ui/home/widgets/join_room_dialog.dart index 632b9e1..f4f877d 100644 --- a/lib/ui/home/widgets/join_room_dialog.dart +++ b/lib/ui/home/widgets/join_room_dialog.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:appsagainsthumanity/data/firestore.dart'; import 'package:appsagainsthumanity/internal.dart'; import 'package:flutter/material.dart'; @@ -33,7 +34,7 @@ class _JoinRoomDialogState extends State { border: OutlineInputBorder(), labelText: 'Game ID', ), - maxLength: 5, + maxLength: FirebaseConstants.MAX_GID_SIZE, maxLengthEnforced: true, keyboardType: TextInputType.text, autofocus: true, @@ -43,7 +44,7 @@ class _JoinRoomDialogState extends State { Navigator.of(context).pop(value); }, validator: (value) { - if (value.length != 5) { + if (value.length != FirebaseConstants.MAX_GID_SIZE) { return 'Please enter a valid game id'; } return null; From 0106042561e0a4c2a490b60fd90168e99493197a Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Fri, 24 Apr 2020 17:46:33 -0400 Subject: [PATCH 09/67] =?UTF-8?q?Added=20release=20notes=20for=20next=20bu?= =?UTF-8?q?ild=20Incrementing=20build=20no.=20for=20next=20release=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - android/.gitignore | 2 +- android/distribution/release-notes.txt | 5 +++++ pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 android/distribution/release-notes.txt diff --git a/.gitignore b/.gitignore index 3124ee4..61ef6d3 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,6 @@ # Misc tools lib/config.json lib/config.dart -credentials.json *.g.dart web/firebase_config.js diff --git a/android/.gitignore b/android/.gitignore index 24ed0af..69fd9d6 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -5,6 +5,6 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java -distribution/*.jks +distribution/aah.jks key.properties google-services.json diff --git a/android/distribution/release-notes.txt b/android/distribution/release-notes.txt new file mode 100644 index 0000000..7519586 --- /dev/null +++ b/android/distribution/release-notes.txt @@ -0,0 +1,5 @@ +* Fixed Prompt Macros +* Added Push Notifications +* Fixed how the player list can scroll +* Fixed some end of game functions +* Optimizations and improvements for the app diff --git a/pubspec.yaml b/pubspec.yaml index c58d212..09b5ffe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+2 +version: 1.0.0+3 environment: sdk: ">=2.7.0 <3.0.0" From 052034367a2aa2e2a0498af048ad3662e4ab5e3b Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Fri, 24 Apr 2020 17:47:36 -0400 Subject: [PATCH 10/67] Create CI.yml --- .github/workflows/CI.yml | 69 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .github/workflows/CI.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..84dde13 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,69 @@ +name: Flutter CI + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install Flutter + uses: subosito/flutter-action@v1 + with: + channel: 'dev' # or: 'dev' or 'beta' + + - name: Fetch dependencies + run: flutter pub get + + - name: Run Tests + run: flutter test + + deploy-android: + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install Flutter + uses: subosito/flutter-action@v1 + with: + channel: 'dev' # or: 'dev' or 'beta' + + - name: Fetch dependencies + run: flutter pub get + + - name: Setup Secrets + run: | + echo $GOOGLE_SERVICES_JSON | tee android/app/google-services.json + echo $CONFIG_JSON | tee lib/config.json + echo $KEY_PROPERTIES | tee android/key.properties + echo $SIGNING_KEY | base64 --decode > android/distribution/aah.jks + env: + GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} + CONFIG_JSON: ${{ secrets.CONFIG }} + KEY_PROPERTIES: ${{ secrets.KEY_PROPERTIES }} + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + + - name: Run Code Generate + run: ./regenerate.sh + + - name: Build APK + run: flutter build apk + + - name: Upload to Firebase App Distribution + uses: wzieba/Firebase-Distribution-Github-Action@v1.2.1 + with: + appId: ${{secrets.ANDROID_FIREBASE_APP_ID}} + token: ${{secrets.FIREBASE_TOKEN}} + groups: 52inc,friends-&-family + releaseNotesFile: android/distribution/release_notes.txt + file: ${{ env.SIGNED_RELEASE_FILE }} + From e8863b39bf056a2a99ac90571c2fe5871810d0c4 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Fri, 24 Apr 2020 17:48:00 -0400 Subject: [PATCH 11/67] Merging ci workflow --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 84dde13..f7ac73b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -35,7 +35,7 @@ jobs: - name: Install Flutter uses: subosito/flutter-action@v1 with: - channel: 'dev' # or: 'dev' or 'beta' + channel: 'dev' - name: Fetch dependencies run: flutter pub get From 3ab05ba64db53895eae5faac41476e23b54eb8f4 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Fri, 24 Apr 2020 17:53:09 -0400 Subject: [PATCH 12/67] Adding dart container to CI jobs --- .github/workflows/CI.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f7ac73b..cdd2d76 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,7 +11,8 @@ on: jobs: test: runs-on: ubuntu-latest - + container: + image: google/dart:latest steps: - uses: actions/checkout@v2 @@ -29,6 +30,8 @@ jobs: deploy-android: needs: test runs-on: ubuntu-latest + container: + image: google/dart:latest steps: - uses: actions/checkout@v2 From 51d6668a3d63edddf97e35d151bd17e31d48fbde Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Sat, 25 Apr 2020 16:37:19 -0400 Subject: [PATCH 13/67] Fixed #1 - Finished adding Dynamic Links --- .../game/firestore_game_repository.dart | 29 ++++++++-- lib/data/features/game/game_repository.dart | 2 +- lib/internal/dynamic_links.dart | 58 +++++++++++++++++++ lib/internal/push.dart | 17 ++++-- .../screens/waiting/waiting_room_screen.dart | 7 ++- pubspec.lock | 14 +++++ pubspec.yaml | 2 + 7 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 lib/internal/dynamic_links.dart diff --git a/lib/data/features/game/firestore_game_repository.dart b/lib/data/features/game/firestore_game_repository.dart index 64229e0..9be1054 100644 --- a/lib/data/features/game/firestore_game_repository.dart +++ b/lib/data/features/game/firestore_game_repository.dart @@ -42,6 +42,7 @@ class FirestoreGameRepository extends GameRepository { }); } + @override Future joinGame(String gid) { return currentUserOrThrow((firebaseUser) async { @@ -85,12 +86,21 @@ class FirestoreGameRepository extends GameRepository { } @override - Future getGame(String gameDocumentId) async { + Future getGame(String gameDocumentId, {bool andJoin = false}) async { var gameDocument = db.collection(FirebaseConstants.COLLECTION_GAMES) .document(gameDocumentId); var snapshot = await gameDocument.get(); - return Game.fromDocument(snapshot); + var game = Game.fromDocument(snapshot); + + // TODO: Update this to be joinable at anypoint in hte game if the game is setup for it + if (andJoin && game.state == GameState.waitingRoom) { + await _addSelfToGame(gameDocumentId, game); + } else if (andJoin) { + throw 'You cannot join a game that has already started'; + } + + return game; } @override @@ -211,9 +221,16 @@ class FirestoreGameRepository extends GameRepository { .collection(FirebaseConstants.COLLECTION_DOWNVOTES) .document(FirebaseConstants.DOCUMENT_TALLY); - await gameDocument.setData({ - 'votes': FieldValue.arrayUnion([firebaseUser.uid]) - }); + var snapshot = await gameDocument.get(); + if (snapshot.exists) { + await gameDocument.updateData({ + 'votes': FieldValue.arrayUnion([firebaseUser.uid]) + }); + } else { + await gameDocument.setData({ + 'votes': [firebaseUser.uid] + }); + } }); } @@ -245,7 +262,7 @@ class FirestoreGameRepository extends GameRepository { var player = Player( id: user.id, name: user.name, - avatarUrl: user.avatarUrl, + avatarUrl: user.avatarUrl ); // Write self to game's players diff --git a/lib/data/features/game/game_repository.dart b/lib/data/features/game/game_repository.dart index 4f7084a..330924e 100644 --- a/lib/data/features/game/game_repository.dart +++ b/lib/data/features/game/game_repository.dart @@ -18,7 +18,7 @@ abstract class GameRepository { Future findGame(String gid); /// Get a game by it's actual document id - Future getGame(String gameDocumentId); + Future getGame(String gameDocumentId, {bool andJoin = false}); /// Return a list of games that you have joined in the past Stream> observeJoinedGames(); diff --git a/lib/internal/dynamic_links.dart b/lib/internal/dynamic_links.dart new file mode 100644 index 0000000..67c8c8c --- /dev/null +++ b/lib/internal/dynamic_links.dart @@ -0,0 +1,58 @@ +import 'package:firebase_dynamic_links/firebase_dynamic_links.dart'; +import 'package:flutter/material.dart'; + +class DynamicLinks { + DynamicLinks._(); + + static Future createLink(String gameDocumentId) async { + final DynamicLinkParameters params = DynamicLinkParameters( + uriPrefix: "https://appsagainsthumanity.com/games", + link: Uri.parse("https://appsagainsthumanity.com/games/$gameDocumentId"), + androidParameters: AndroidParameters( + packageName: 'com.ftinc.appsagainsthumanity', + ), + iosParameters: IosParameters( + appStoreId: "1509268296", + bundleId: "com.ftinc.appsagainsthumanity", + ), + socialMetaTagParameters: SocialMetaTagParameters( + title: "Come join my game!", + description: "Play a game of Apps Against Humanity", + ), + ); + + var shortLink = await params.buildShortLink(); + return shortLink.shortUrl; + } + + static void initDynamicLinks(BuildContext context, void Function(String gameId) callback) async { + final PendingDynamicLinkData data = await FirebaseDynamicLinks.instance.getInitialLink(); + final Uri deepLink = data?.link; + + if (deepLink != null) { + var lastSegment = deepLink.pathSegments.last; + if (lastSegment != null) { + print("Joining $lastSegment from dynamic link"); + callback(lastSegment); + } + } + + FirebaseDynamicLinks.instance.onLink( + onSuccess: (PendingDynamicLinkData dynamicLink) async { + final Uri deepLink = dynamicLink?.link; + + if (deepLink != null) { + var lastSegment = deepLink.pathSegments.last; + if (lastSegment != null) { + print("Joining $lastSegment from dynamic link"); + callback(lastSegment); + } + } + }, + onError: (OnLinkErrorException e) async { + print('onLinkError'); + print(e.message); + } + ); + } +} diff --git a/lib/internal/push.dart b/lib/internal/push.dart index 43fc291..a281930 100644 --- a/lib/internal/push.dart +++ b/lib/internal/push.dart @@ -5,6 +5,7 @@ import 'package:appsagainsthumanity/data/app_preferences.dart'; import 'package:appsagainsthumanity/data/features/devices/device_repository.dart'; import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/data/features/game/model/turn.dart'; +import 'package:appsagainsthumanity/internal/dynamic_links.dart'; import 'package:appsagainsthumanity/ui/routes.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/cupertino.dart'; @@ -90,6 +91,9 @@ class _PushNavigatorState extends State { handleNotificationClick(message); }, ); + + /// let's go ahead and sign up here to handle dynamic links + DynamicLinks.initDynamicLinks(context, (gameId) => navigateToGame(gameId, andJoin: true)); } @override @@ -100,6 +104,12 @@ class _PushNavigatorState extends State { void handleNotificationClick(Map message) async { var gameId = Platform.isAndroid ? message['data']['gameId'] : message['gameId']; if (gameId != null && gameId is String && gameId.isNotEmpty) { + navigateToGame(gameId); + } + } + + void navigateToGame(String gameId, {bool andJoin = false}) async { + if (gameId != null && gameId.isNotEmpty) { var currentRoute = Routes.routeTracer.currentRoute; print(currentRoute); if (currentRoute.settings.arguments == gameId) { @@ -108,7 +118,7 @@ class _PushNavigatorState extends State { return; } try { - var game = await _gameRepository.getGame(gameId); + var game = await _gameRepository.getGame(gameId, andJoin: andJoin); if (game != null) { /* * Neuter the turn winner out of the otherwise the winner bottom sheet will never show @@ -135,8 +145,8 @@ class _PushNavigatorState extends State { } else if (currentRoute is MaterialPageRoute){ print("User is not in a game or homescreen"); currentRoute.navigator - ..popUntil((route) => route.settings.name == "/") - ..push(GamePageRoute(game)); + ..popUntil((route) => route.settings.name == "/") + ..push(GamePageRoute(game)); } } } catch (e) { @@ -145,5 +155,4 @@ class _PushNavigatorState extends State { } } - } diff --git a/lib/ui/game/screens/waiting/waiting_room_screen.dart b/lib/ui/game/screens/waiting/waiting_room_screen.dart index f33b86f..fa16715 100644 --- a/lib/ui/game/screens/waiting/waiting_room_screen.dart +++ b/lib/ui/game/screens/waiting/waiting_room_screen.dart @@ -1,11 +1,13 @@ import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/data/features/game/model/player.dart'; import 'package:appsagainsthumanity/internal.dart'; +import 'package:appsagainsthumanity/internal/dynamic_links.dart'; import 'package:appsagainsthumanity/ui/game/bloc/bloc.dart'; import 'package:appsagainsthumanity/ui/widgets/player_circle_avatar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:share/share.dart'; class WaitingRoomScreen extends StatelessWidget { @override @@ -58,8 +60,9 @@ class WaitingRoomScreen extends StatelessWidget { actions: [ IconButton( icon: Icon(Icons.group_add), - onPressed: () { - // TODO: Share link (or game code) to system share mechanisms + onPressed: () async { + var link = await DynamicLinks.createLink(state.game.id); + await Share.share(link.toString()); }, ) ], diff --git a/pubspec.lock b/pubspec.lock index 2c791e3..28b61e1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -302,6 +302,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.1+2" + firebase_dynamic_links: + dependency: "direct main" + description: + name: firebase_dynamic_links + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.0+11" firebase_messaging: dependency: "direct main" description: @@ -665,6 +672,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.23.1" + share: + dependency: "direct main" + description: + name: share + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 09b5ffe..c696ea0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,7 @@ dependencies: path_provider: ^1.6.5 path: ^1.6.4 uuid: ^2.0.4 + share: ^0.6.4 # Architecture and Tools bloc: ^3.0.0 @@ -50,6 +51,7 @@ dependencies: firebase_analytics: ^5.0.11 firebase_storage: ^3.1.3 firebase_messaging: ^6.0.13 + firebase_dynamic_links: ^0.5.0+11 cloud_firestore: ^0.13.4+1 cloud_functions: ^0.4.2+3 google_sign_in: ^4.4.1 From e5f4ea1489e90682bb257bb04c103132eb9eba0a Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Sun, 26 Apr 2020 17:25:40 -0400 Subject: [PATCH 14/67] Fixed #13 - Improved the look and feel of the 'New Game' interface #14 - Reworking the app colors and theming --- assets/1.5x/rando_cardrissian.png | Bin 2709 -> 2792 bytes assets/2.0x/rando_cardrissian.png | Bin 3578 -> 3707 bytes assets/3.0x/rando_cardrissian.png | Bin 5717 -> 5903 bytes assets/4.0x/rando_cardrissian.png | Bin 7737 -> 7935 bytes assets/rando_cardrissian.png | Bin 1800 -> 1850 bytes importer/src/main.ts | 112 ++++--- lib/data/app_preferences.dart | 11 + lib/data/features/cards/cards_repository.dart | 5 +- .../cards/firestore_cards_repository.dart | 9 +- lib/data/features/cards/model/card_set.dart | 35 +++ .../game/firestore_game_repository.dart | 103 +++---- lib/data/features/game/game_repository.dart | 87 +++--- lib/data/features/game/model/game.dart | 32 +- lib/internal/theme.dart | 10 +- lib/ui/creategame/bloc/create_game_bloc.dart | 50 ++- lib/ui/creategame/bloc/create_game_event.dart | 49 ++- lib/ui/creategame/bloc/create_game_state.dart | 39 ++- lib/ui/creategame/create_game_screen.dart | 285 ++++++++++++++---- .../creategame/widgets/CountPreference.dart | 66 ++++ .../complete/completed_game_screen.dart | 2 +- .../screens/gameplay/game_play_screen.dart | 8 +- .../gameplay/widget/game_status_title.dart | 2 +- .../gameplay/widget/judge/judge_bar.dart | 2 +- .../screens/gameplay/widget/player_item.dart | 4 +- .../gameplay/widget/re_deal_button.dart | 63 ++-- .../widget/waiting_player_responses.dart | 2 +- lib/ui/home/home_screen.dart | 22 +- lib/ui/widgets/player_circle_avatar.dart | 29 +- pubspec.lock | 7 + pubspec.yaml | 1 + 30 files changed, 732 insertions(+), 303 deletions(-) create mode 100644 lib/data/features/cards/model/card_set.dart create mode 100644 lib/ui/creategame/widgets/CountPreference.dart diff --git a/assets/1.5x/rando_cardrissian.png b/assets/1.5x/rando_cardrissian.png index fe53db47e64809d2de10b79581cbca18670709ff..fe24756f4b6fb1dd6229c5de683d4a3b40a7daea 100644 GIT binary patch delta 2691 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 delta 2608 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 SCeqme0000DnMd+Pw9%R*oQ7MDW6aU7HQ0PI-c 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 delta 3483 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 diff --git a/assets/4.0x/rando_cardrissian.png b/assets/4.0x/rando_cardrissian.png index 38d8ed4d3f6677d0c1b84e560d289c95d6779701..468c5d82ebe7310c76f1eb30f9ed74e4bd556b19 100644 GIT binary patch delta 7875 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 delta 1691 zcmV;M24wlV4u}qrfq!yIL_t(o3C&qej1)x_e%14{yR!@IF394tfbJriNJPP?K>R_0 zM57Uj#G@BaMo)M&{OG|8UW_r}WIP%S>cJ2J6GIF?A~#|J0xEQ}E`ef{daSFdW6+#$`pxM9RAdaruQTYo2_21WFs(tM(&^eA}v z=}|{JXPm6_ZRerm7w=>$)y^Qjb;IJkcHhxjzb0B%w=-6hoK;GFol~h#)#2mgt&VL8 zpx*IZPkti56Q+4ji|%PvxlK-qni8e=<@$0Ts{=ny*T~1ObmQhV_eqy)Q-dA@Y9B0)7)E&&B6AfkU&^y+5k6n}JgmV=7^^#=#NL_p{zz0Y_a^m|$Pt{-vDT#{evp zB~=0Xd4JB5N@gImzd#$_Jvv>|avs5CCp{2g33~md^mho?y=Y6qhhV*r@^*mp;ZtiC zX9VsgW00AEY&Tw(bf*Mea&Dq5SEuaO+L~4JKICk~WS&c*RhfaSke&;H>bcZs(ZMlg1O5F z2*$REgz<94Fy6}Ew5)^0QWa2!SYq?RzO?X1Xn)))%ZY16Uv>t0>@P5fEC4Cs@Am-c zuqtV^@lQfgNJBlTw!nqs_#}?yh+dN>4U3w5zl~yq*c_Hjb)5!d*sxYRmNQgTn<>Lu zjDOA*CZj4|g)N?gvy!9v&Ye=qP&5+m5(=^x+z%hY0B4Wdb1Vv_&;2HxUHjz0| z$ryjXJ%D5S)gSG)1Qh#;8@bs~bW7@nzKbu*Q*kBrc%~W7(-;;A>1Ub;80=BA? z*JkC{>t&x^E|+DdI9nANo%ZDPSW&LxY0`=XC=_xnt^}}QR{!+1%2Z;u)9{-mP=6Tu zVc5sz+O(`wdTm<)gqC3mpIO=g%QK$<|-xag%P5&i-AJ zmv5AW0nmv2Gc^y@jfB{@)kqIXvVYl3MsgipN!=0teTa_!2*>_HY>t28$D!IT5^!H= zk`7psoHs4Qq;z#wcy6GT8fS?$umnU5rMa{=|rlMcQ zN^%B2M*ed6;Iet?IzLT_D?>X%7nsO^yTW049l;ZS0>!^~3J!ko!i@ZMb$?baqt+uh z=CzH8TBZ=%ZyW_I!G3e0Aitx`GAXXl>|@(6Sb>S{z=Eh;ahlr)&3nj5?~*z6Rt4U;v5_j zHxzDv=5HR+fjZG?^4ab%@_+I$js?9@gNAA=Pc;@`30|uJd6C1r63iX_{K~M0v1IGv zZ*!rM8Bd=S?3uz~=<8iFO7Wg7}rbNQr z=9!ps03D@Pu0C$S2M=Lyo{SX_QBA=c0d2bN3a!_OlCC}?7Ni2+jeqzTIk?bCQrM3? zw;emd1DG_MU=i*TgE$dxLq*dPI9?vb?qz&DNgyhg+y#%^jk)S9oiwf;K79_F_vVhA z`oIRRA;55=|Lp~42#`+O-QTs6H&2a{c_`oPNgdQho$-LMjcse`{40{+7GfI~r>(6K lOZo3zqT9agzxo^E9~2h7kh)z1*P#Fa002ovPDHLkV1jYkKJ5Si diff --git a/importer/src/main.ts b/importer/src/main.ts index 917053b..dc56e3a 100644 --- a/importer/src/main.ts +++ b/importer/src/main.ts @@ -15,11 +15,17 @@ const argv = require('minimist')(process.argv.slice(2)); console.log(argv); // Sheet Variables - const promptLength = argv.pl || 6792; const responseLength = argv.rl || 24413; const documentId = argv.doc || '1lsy7lIwBe-DWOi2PALZPf5DgXHx9MEvKfRw1GaWQkzg'; const sheetId = argv.sheet || '2018240023'; +const cardSetOnly = argv['set-only'] || false; + +type CardSet = { + id: string; + set: string; + source: string; +} // @ts-ignore async function loadAndSavePromptCards(sheet: GoogleSpreadsheetWorksheet) { @@ -27,7 +33,7 @@ async function loadAndSavePromptCards(sheet: GoogleSpreadsheetWorksheet) { await sheet.loadCells(`A2:D${promptLength}`); console.log("Prompt cells loaded"); - const prompts = new Map(); + const prompts = new Map(); for (let i=2; i<=promptLength; i++) { const promptText = sheet.getCellByA1(`A${i}`).value; @@ -39,9 +45,14 @@ async function loadAndSavePromptCards(sheet: GoogleSpreadsheetWorksheet) { const cid = computeCardId(promptSet, promptText); let cards = prompts.get(promptSet); if (!cards) { - cards = []; + const newSet: CardSet = { + id: cleanPath(promptSet), + set: promptSet, + source: sourceSheet + }; + cards = [newSet, []]; } - cards.push({ + cards[1].push({ cid: cid, text: promptText, special: promptSpecial, @@ -59,31 +70,32 @@ async function loadAndSavePromptCards(sheet: GoogleSpreadsheetWorksheet) { // Set the set master document await cardSetDocument.set({ name: promptSet, - prompts: cards.length, - promptIndexes: cards.map((card) => card.cid) + source: cards[0].source, + prompts: cards[1].length, + promptIndexes: cards[1].map((card) => card.cid) }, { merge: true }); - const promptsCollection = cardSetDocument.collection('prompts'); - - let currentBatchCount = 0; - let batch = db.batch(); - for (let prompt of cards) { - try { - const document = promptsCollection.doc(hashDocumentId(prompt.text)); - if (currentBatchCount >= 500) { - await batch.commit(); - batch = db.batch(); - currentBatchCount = 0; - console.log("Batch committed to Firebase"); + if (!cardSetOnly) { + const promptsCollection = cardSetDocument.collection('prompts'); + let currentBatchCount = 0; + let batch = db.batch(); + for (let prompt of cards[1]) { + try { + const document = promptsCollection.doc(hashDocumentId(prompt.text)); + if (currentBatchCount >= 500) { + await batch.commit(); + batch = db.batch(); + currentBatchCount = 0; + console.log("Batch committed to Firebase"); + } + batch.set(document, prompt); + currentBatchCount += 1; + } catch (e) { + console.log("Error processing prompt card: " + e); } - batch.set(document, prompt); - currentBatchCount += 1; - } catch (e) { - console.log("Error processing prompt card: " + e); } + await batch.commit(); } - - await batch.commit(); } } @@ -93,7 +105,7 @@ async function loadAndSaveResponseCards(sheet: GoogleSpreadsheetWorksheet) { await sheet.loadCells(`G2:I${responseLength}`); console.log("Response cells loaded"); - const responses = new Map(); + const responses = new Map(); for (let i=2; i<=responseLength; i++) { const responseText = sheet.getCellByA1(`G${i}`).value?.toString(); @@ -104,9 +116,14 @@ async function loadAndSaveResponseCards(sheet: GoogleSpreadsheetWorksheet) { const cid = computeCardId(responseSet, responseText); let cards = responses.get(responseSet); if (!cards) { - cards = []; + const newSet: CardSet = { + id: cleanPath(responseSet), + set: responseSet, + source: sourceSheet + }; + cards = [newSet, []]; } - cards.push({ + cards[1].push({ cid: cid, text: responseText, set: responseSet, @@ -122,31 +139,32 @@ async function loadAndSaveResponseCards(sheet: GoogleSpreadsheetWorksheet) { await cardSetDocument.set({ name: responseSet, - responses: cards.length, - responseIndexes: cards.map((card) => card.cid) + source: cards[0].source, + responses: cards[1].length, + responseIndexes: cards[1].map((card) => card.cid) }, { merge: true }); - const responsesCollection = cardSetDocument.collection('responses'); - - let currentBatchCount = 0; - let batch = db.batch(); - for (let response of cards) { - try { - const document = responsesCollection.doc(hashDocumentId(response.text)); - if (currentBatchCount >= 500) { - await batch.commit(); - batch = db.batch(); - currentBatchCount = 0; - console.log("Batch committed to Firebase"); + if (!cardSetOnly) { + const responsesCollection = cardSetDocument.collection('responses'); + let currentBatchCount = 0; + let batch = db.batch(); + for (let response of cards[1]) { + try { + const document = responsesCollection.doc(hashDocumentId(response.text)); + if (currentBatchCount >= 500) { + await batch.commit(); + batch = db.batch(); + currentBatchCount = 0; + console.log("Batch committed to Firebase"); + } + batch.set(document, response); + currentBatchCount += 1; + } catch (e) { + console.log("Error processing response card: " + e); } - batch.set(document, response); - currentBatchCount += 1; - } catch (e) { - console.log("Error processing response card: " + e); } + await batch.commit(); } - - await batch.commit(); } } diff --git a/lib/data/app_preferences.dart b/lib/data/app_preferences.dart index a4bb579..eaf9ddd 100644 --- a/lib/data/app_preferences.dart +++ b/lib/data/app_preferences.dart @@ -14,6 +14,8 @@ class AppPreferences { static const KEY_AGREE_TO_TERMS = "agree_to_terms_of_service"; static const KEY_PUSH_TOKEN = "push_token"; static const KEY_DEVICE_ID = "device_id"; + static const KEY_PRIZES_TO_WIN = "prizes_to_win"; + static const KEY_PLAYER_LIMIT = "player_limit"; SharedPreferences _prefs; @@ -23,12 +25,21 @@ class AppPreferences { return _instance; } + /// Whether or not the user has agreed to TOS bool get agreedToTermsOfService => _prefs.getBool(KEY_AGREE_TO_TERMS) ?? false; set agreedToTermsOfService(bool agreed) => _prefs.setBool(KEY_AGREE_TO_TERMS, agreed); + /// The Push Token String get pushToken => _prefs.getString(KEY_PUSH_TOKEN); set pushToken(String token) => _prefs.setString(KEY_PUSH_TOKEN, token); + int get prizesToWin => _prefs.getInt(KEY_PRIZES_TO_WIN) ?? 7; + set prizesToWin(int value) => _prefs.setInt(KEY_PRIZES_TO_WIN, value); + + int get playerLimit => _prefs.getInt(KEY_PLAYER_LIMIT) ?? 15; + set playerLimit(int value) => _prefs.setInt(KEY_PLAYER_LIMIT, value); + + /// The persistent device id String get deviceId { var dId = _prefs.getString(KEY_DEVICE_ID); if (dId == null) { diff --git a/lib/data/features/cards/cards_repository.dart b/lib/data/features/cards/cards_repository.dart index 8dd51a5..7389673 100644 --- a/lib/data/features/cards/cards_repository.dart +++ b/lib/data/features/cards/cards_repository.dart @@ -1,4 +1,7 @@ +import 'package:appsagainsthumanity/data/features/cards/model/card_set.dart'; + abstract class CardsRepository{ - Future> getAvailableCardSets(); + /// Get the list of cardsets that you can use + Future> getAvailableCardSets(); } diff --git a/lib/data/features/cards/firestore_cards_repository.dart b/lib/data/features/cards/firestore_cards_repository.dart index 898e421..e9cfb8a 100644 --- a/lib/data/features/cards/firestore_cards_repository.dart +++ b/lib/data/features/cards/firestore_cards_repository.dart @@ -1,4 +1,5 @@ import 'package:appsagainsthumanity/data/features/cards/cards_repository.dart'; +import 'package:appsagainsthumanity/data/features/cards/model/card_set.dart'; import 'package:appsagainsthumanity/data/firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; @@ -9,12 +10,16 @@ class FirestoreCardsRepository extends CardsRepository { : _db = firestore ?? Firestore.instance; @override - Future> getAvailableCardSets() { + Future> getAvailableCardSets() { return currentUserOrThrow((firebaseUser) async { var cardSetsCollection = _db.collection(FirebaseConstants.COLLECTION_CARD_SETS); var snapshots = await cardSetsCollection.getDocuments(); print("${snapshots.documents.length} Card sets found"); - return snapshots.documents.map((e) => e.documentID).toList(); + return snapshots.documents.map((e) { + var cardSet = CardSet.fromJson(e.data); + cardSet.id = e.documentID; + return cardSet; + }).toList(); }); } } diff --git a/lib/data/features/cards/model/card_set.dart b/lib/data/features/cards/model/card_set.dart new file mode 100644 index 0000000..61489f6 --- /dev/null +++ b/lib/data/features/cards/model/card_set.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'card_set.g.dart'; + +@JsonSerializable() +class CardSet extends Equatable { + @JsonKey(ignore: true) + String id; + + final String name; + final int prompts; + final int responses; + final String source; + + CardSet({ + this.id, + this.name, + this.prompts, + this.responses, + this.source, + }); + + factory CardSet.fromJson(Map json) => _$CardSetFromJson(json); + + Map toJson() => _$CardSetToJson(this); + + @override + String toString() { + return 'CardSet{id: $id, name: $name, prompts: $prompts, responses: $responses, source: $source}'; + } + + @override + List get props => [id, name, prompts, responses, source]; +} diff --git a/lib/data/features/game/firestore_game_repository.dart b/lib/data/features/game/firestore_game_repository.dart index 9be1054..30e72ff 100644 --- a/lib/data/features/game/firestore_game_repository.dart +++ b/lib/data/features/game/firestore_game_repository.dart @@ -1,5 +1,5 @@ import 'dart:math'; - +import 'package:appsagainsthumanity/data/features/cards/model/card_set.dart'; import 'package:appsagainsthumanity/data/features/cards/model/response_card.dart'; import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/data/features/game/model/game.dart'; @@ -22,7 +22,13 @@ class FirestoreGameRepository extends GameRepository { db = firestore ?? Firestore.instance; @override - Future createGame(KtSet cardSets, {int prizesToWin = 10}) { + Future createGame( + KtSet cardSets, { + int prizesToWin = Game.PRIZES_TO_WIN, + int playerLimit = Game.PLAYER_LIMIT, + bool pick2Enabled = true, + bool draw2Pick3Enabled = true, + }) { return currentUserOrThrow((firebaseUser) async { var newGameDoc = db.collection(FirebaseConstants.COLLECTION_GAMES).document(); var game = Game( @@ -31,7 +37,10 @@ class FirestoreGameRepository extends GameRepository { ownerId: firebaseUser.uid, state: GameState.waitingRoom, prizesToWin: prizesToWin, - cardSets: cardSets.asSet(), + playerLimit: playerLimit, + pick2Enabled: pick2Enabled, + draw2Pick3Enabled: draw2Pick3Enabled, + cardSets: cardSets.map((c) => c.id).asList().toSet(), ); await newGameDoc.setData(game.toJson()); @@ -42,15 +51,11 @@ class FirestoreGameRepository extends GameRepository { }); } - @override Future joinGame(String gid) { return currentUserOrThrow((firebaseUser) async { var snapshot = - await db.collection(FirebaseConstants.COLLECTION_GAMES) - .where('gid', isEqualTo: gid) - .limit(1) - .getDocuments(); + await db.collection(FirebaseConstants.COLLECTION_GAMES).where('gid', isEqualTo: gid).limit(1).getDocuments(); if (snapshot != null && snapshot.documents.isNotEmpty) { var gameDocument = snapshot.documents.first; @@ -71,11 +76,8 @@ class FirestoreGameRepository extends GameRepository { @override Future findGame(String gid) async { - var snapshots = await db - .collection(FirebaseConstants.COLLECTION_GAMES) - .where('gid', isEqualTo: gid) - .limit(1) - .getDocuments(); + var snapshots = + await db.collection(FirebaseConstants.COLLECTION_GAMES).where('gid', isEqualTo: gid).limit(1).getDocuments(); if (snapshots != null && snapshots.documents.isNotEmpty) { var document = snapshots.documents.first; @@ -87,8 +89,7 @@ class FirestoreGameRepository extends GameRepository { @override Future getGame(String gameDocumentId, {bool andJoin = false}) async { - var gameDocument = db.collection(FirebaseConstants.COLLECTION_GAMES) - .document(gameDocumentId); + var gameDocument = db.collection(FirebaseConstants.COLLECTION_GAMES).document(gameDocumentId); var snapshot = await gameDocument.get(); var game = Game.fromDocument(snapshot); @@ -140,14 +141,13 @@ class FirestoreGameRepository extends GameRepository { .collection(FirebaseConstants.COLLECTION_DOWNVOTES) .document(FirebaseConstants.DOCUMENT_TALLY); - return collection.snapshots() - .map((snapshot) { - if (snapshot.data != null) { - return List.from(snapshot.data['votes'] ?? []); - } else { - return []; - } - }); + return collection.snapshots().map((snapshot) { + if (snapshot.data != null) { + return List.from(snapshot.data['votes'] ?? []); + } else { + return []; + } + }); } @override @@ -159,11 +159,10 @@ class FirestoreGameRepository extends GameRepository { .document(FirebaseConstants.DOCUMENT_RANDO_CARDRISSIAN); var rando = Player( - id: FirebaseConstants.DOCUMENT_RANDO_CARDRISSIAN, - name: "Rando Cardrissian", - avatarUrl: null, - isRandoCardrissian: true - ); + id: FirebaseConstants.DOCUMENT_RANDO_CARDRISSIAN, + name: "Rando Cardrissian", + avatarUrl: null, + isRandoCardrissian: true); await document.setData(rando.toJson()); } @@ -171,9 +170,7 @@ class FirestoreGameRepository extends GameRepository { @override Future startGame(String gameDocumentId) async { final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(functionName: 'startGame'); - dynamic response = await callable.call({ - 'game_id': gameDocumentId - }); + dynamic response = await callable.call({'game_id': gameDocumentId}); print("Start game Successful! $response"); } @@ -183,13 +180,9 @@ class FirestoreGameRepository extends GameRepository { // 1. Remove the cards from the player's hand // 2. Add the cards as a response to the current game's turn return currentUserOrThrow((firebaseUser) async { - var gameDocument = db - .collection(FirebaseConstants.COLLECTION_GAMES) - .document(gameDocumentId); + var gameDocument = db.collection(FirebaseConstants.COLLECTION_GAMES).document(gameDocumentId); - var playerDocument = gameDocument - .collection(FirebaseConstants.COLLECTION_PLAYERS) - .document(firebaseUser.uid); + var playerDocument = gameDocument.collection(FirebaseConstants.COLLECTION_PLAYERS).document(firebaseUser.uid); var cardData = cards.map((e) => e.toJson()).toList(); @@ -201,13 +194,9 @@ class FirestoreGameRepository extends GameRepository { return cards.firstWhere((c) => c.cid == element.cid, orElse: () => null) != null; }); - await transaction.update(playerDocument, { - 'hand': player.hand.map((e) => e.toJson()).toList() - }); + await transaction.update(playerDocument, {'hand': player.hand.map((e) => e.toJson()).toList()}); - await transaction.update(gameDocument, { - 'turn.responses.${firebaseUser.uid}': cardData - }); + await transaction.update(gameDocument, {'turn.responses.${firebaseUser.uid}': cardData}); }); }); } @@ -238,10 +227,7 @@ class FirestoreGameRepository extends GameRepository { Future reDealHand(String gameDocumentId) { return currentUserOrThrow((firebaseUser) async { final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(functionName: 'reDealHand'); - dynamic response = await callable.call({ - 'game_id': gameDocumentId - }); - + dynamic response = await callable.call({'game_id': gameDocumentId}); print("Hand re-dealt successfully! $response"); }); } @@ -249,21 +235,26 @@ class FirestoreGameRepository extends GameRepository { @override Future pickWinner(String gameDocumentId, String playerId) async { final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(functionName: 'pickWinner'); - dynamic response = await callable.call({ - 'game_id': gameDocumentId, - 'player_id': playerId - }); + dynamic response = await callable.call({'game_id': gameDocumentId, 'player_id': playerId}); print("Winner picked Successful! $response"); } Future _addSelfToGame(String gameDocumentId, Game game) async { + // first check if game CAN be joined + var limit = game.playerLimit ?? Game.PLAYER_LIMIT; + var players = await db + .collection(FirebaseConstants.COLLECTION_GAMES) + .document(game.id) + .collection(FirebaseConstants.COLLECTION_PLAYERS) + .getDocuments(); + + if (players.documents.length >= limit) { + throw 'Unable to join. This game is already full'; + } + var user = await userRepository.getUser(); - var player = Player( - id: user.id, - name: user.name, - avatarUrl: user.avatarUrl - ); + var player = Player(id: user.id, name: user.name, avatarUrl: user.avatarUrl); // Write self to game's players await db diff --git a/lib/data/features/game/game_repository.dart b/lib/data/features/game/game_repository.dart index 330924e..66c02e3 100644 --- a/lib/data/features/game/game_repository.dart +++ b/lib/data/features/game/game_repository.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:appsagainsthumanity/data/features/cards/model/card_set.dart'; import 'package:appsagainsthumanity/data/features/cards/model/response_card.dart'; import 'package:appsagainsthumanity/data/features/game/model/game.dart'; import 'package:appsagainsthumanity/data/features/game/model/game_state.dart'; @@ -8,58 +9,64 @@ import 'package:kt_dart/collection.dart'; abstract class GameRepository { - /// Create a new game with the provided list of card sets - Future createGame(KtSet cardSets, { int prizesToWin = 10 }); + /// Create a new game with the provided list of card sets + Future createGame( + KtSet cardSets, { + int prizesToWin = Game.PRIZES_TO_WIN, + int playerLimit = Game.PLAYER_LIMIT, + bool pick2Enabled = true, + bool draw2Pick3Enabled = true, + }); - /// Join an existing game using the [gid] game id code - Future joinGame(String gid); + /// Join an existing game using the [gid] game id code + Future joinGame(String gid); - /// Find an existing game using the [gid] game id code - Future findGame(String gid); + /// Find an existing game using the [gid] game id code + Future findGame(String gid); - /// Get a game by it's actual document id - Future getGame(String gameDocumentId, {bool andJoin = false}); + /// Get a game by it's actual document id + Future getGame(String gameDocumentId, {bool andJoin = false}); - /// Return a list of games that you have joined in the past - Stream> observeJoinedGames(); + /// Return a list of games that you have joined in the past + Stream> observeJoinedGames(); - /// Observe any changes to a game state by it's [gameDocumentId] - Stream observeGame(String gameDocumentId); + /// Observe any changes to a game state by it's [gameDocumentId] + Stream observeGame(String gameDocumentId); - /// Observe any changes to the players of a game by it's - /// [gameDocumentId] - Stream> observePlayers(String gameDocumentId); + /// Observe any changes to the players of a game by it's + /// [gameDocumentId] + Stream> observePlayers(String gameDocumentId); - /// Observe any changes to the downvote tally by it's [gameDocumentId] - Stream> observeDownvotes(String gameDocumentId); + /// Observe any changes to the downvote tally by it's [gameDocumentId] + Stream> observeDownvotes(String gameDocumentId); - /// Add Rando Cardrissian to the game - /// [gid] the game to add him to - Future addRandoCardrissian(String gameDocumentId); + /// Add Rando Cardrissian to the game + /// [gid] the game to add him to + Future addRandoCardrissian(String gameDocumentId); - /// Start a game that is in it's [GameState.waitingRoom] state - /// The gamescreen should pick up the game state change and update the UI - /// accordingly - Future startGame(String gameDocumentId); + /// Start a game that is in it's [GameState.waitingRoom] state + /// The gamescreen should pick up the game state change and update the UI + /// accordingly + Future startGame(String gameDocumentId); - /// Submit your responses for the current turn, if you are not a judge, and - /// you haven't submitted your response already - Future submitResponse(String gameDocumentId, List cards); + /// Submit your responses for the current turn, if you are not a judge, and + /// you haven't submitted your response already + Future submitResponse(String gameDocumentId, List cards); - /// Downvote the current prompt card. If enough downvotes are casted - /// then a new prompt is drawn for this turn and the current judge remains - Future downVoteCurrentPrompt(String gameDocumentId); + /// Downvote the current prompt card. If enough downvotes are casted + /// then a new prompt is drawn for this turn and the current judge remains + Future downVoteCurrentPrompt(String gameDocumentId); - /// Re-deal your hand in exchange for one prize card, if you have one - Future reDealHand(String gameDocumentId); + /// Re-deal your hand in exchange for one prize card, if you have one + Future reDealHand(String gameDocumentId); - ////////////////////// - // Judge Methods - ////////////////////// + ////////////////////// + // Judge Methods + ////////////////////// - /// Pick the winner of the turn that you are judging. This will fail if: - /// A. You are not the judge - /// B. All responses are not in yet - /// C. The turn hasn't been rotated yet and your previous pick still persists - Future pickWinner(String gameDocumentId, String playerId); + /// Pick the winner of the turn that you are judging. This will fail if: + /// A. You are not the judge + /// B. All responses are not in yet + /// C. The turn hasn't been rotated yet and your previous pick still persists + Future pickWinner(String gameDocumentId, String playerId); } diff --git a/lib/data/features/game/model/game.dart b/lib/data/features/game/model/game.dart index 1f0ef60..ca20aaa 100644 --- a/lib/data/features/game/model/game.dart +++ b/lib/data/features/game/model/game.dart @@ -8,6 +8,9 @@ part 'game.g.dart'; @JsonSerializable() class Game { + static const PRIZES_TO_WIN = 7; + static const PLAYER_LIMIT = 30; + @JsonKey(ignore: true) String id; final String gid; @@ -15,23 +18,30 @@ class Game { final GameState state; final int round; final int prizesToWin; + final int playerLimit; + final bool pick2Enabled; + final bool draw2Pick3Enabled; final List judgeRotation; final Set cardSets; @JsonKey(toJson: _turnToJson) final Turn turn; final String winner; - Game( - {this.id, - @required this.gid, - @required this.ownerId, - @required this.state, - @required this.cardSets, - this.round = 1, - this.prizesToWin = 10, - this.judgeRotation, - this.turn, - this.winner}); + Game({ + this.id, + @required this.gid, + @required this.ownerId, + @required this.state, + @required this.cardSets, + this.round = 1, + this.prizesToWin = PRIZES_TO_WIN, + this.playerLimit = PLAYER_LIMIT, + this.pick2Enabled = true, + this.draw2Pick3Enabled = true, + this.judgeRotation, + this.turn, + this.winner, + }); factory Game.fromDocument(DocumentSnapshot snapshot) { var game = Game.fromJson(snapshot.data); diff --git a/lib/internal/theme.dart b/lib/internal/theme.dart index dc021b4..5a1fec3 100644 --- a/lib/internal/theme.dart +++ b/lib/internal/theme.dart @@ -3,15 +3,17 @@ import 'package:flutter/material.dart'; class AppColors { AppColors._(); - static const primary = Color(0xFFF44336); - static const primaryDark = Color(0xFFB71C1C); - static const primaryVariant = Color(0xFFEF5350); + static const primary = Color(0xFFAB47BC); + static const primaryDark = Color(0xFF790e8b); + static const primaryVariant = Color(0xFFdf78ef); static const secondary = Color(0xFF00BCD4); static const secondaryLight = Color(0xFF80DEEA); static const secondaryDark = Color(0xFF006064); static const surface = Color(0xFF3C3C3C); + static const surfaceLight = Color(0xFF4F4F4F); + static const surfaceDark = Color(0xFF303030); static const responseCardBackground = Color(0xFFF2F2F2); } @@ -24,7 +26,7 @@ class AppThemes { primaryColor: AppColors.primary, primaryColorDark: AppColors.primaryDark, primaryColorLight: AppColors.primaryVariant, - accentColor: AppColors.secondary, + accentColor: AppColors.primary, canvasColor: AppColors.surface ); } diff --git a/lib/ui/creategame/bloc/create_game_bloc.dart b/lib/ui/creategame/bloc/create_game_bloc.dart index 6ec992c..627fb16 100644 --- a/lib/ui/creategame/bloc/create_game_bloc.dart +++ b/lib/ui/creategame/bloc/create_game_bloc.dart @@ -1,3 +1,4 @@ +import 'package:appsagainsthumanity/data/app_preferences.dart'; import 'package:appsagainsthumanity/data/features/cards/cards_repository.dart'; import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/ui/creategame/bloc/create_game_event.dart'; @@ -6,14 +7,13 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kt_dart/kt.dart'; class CreateGameBloc extends Bloc { - final Set cardSets; final CardsRepository cardsRepository; final GameRepository gameRepository; - CreateGameBloc(this.cardSets, this.cardsRepository, this.gameRepository,); + CreateGameBloc(this.cardsRepository, this.gameRepository); @override - CreateGameState get initialState => CreateGameState.loading(sets: cardSets); + CreateGameState get initialState => CreateGameState.empty(); @override Stream mapEventToState(CreateGameEvent event) async* { @@ -21,6 +21,16 @@ class CreateGameBloc extends Bloc { yield* _mapScreenLoadedToState(); } else if (event is CardSetSelected) { yield* _mapCardSetSelectedToState(event); + } else if (event is CardSourceSelected) { + yield* _mapCardSourceSelectedToState(event); + } else if (event is ChangePrizesToWin) { + yield* _mapChangePrizesToWinToState(event); + } else if (event is ChangePlayerLimit) { + yield* _mapChangePlayerLimitToState(event); + } else if (event is ChangePick2Enabled) { + yield* _mapChangePick2EnabledToState(event); + } else if (event is ChangeDraw2Pick3Enabled) { + yield* _mapChangeDraw2Pick3EnabledToState(event); } } @@ -40,4 +50,38 @@ class CreateGameBloc extends Bloc { yield state.copyWith(selectedSets: state.selectedSets.plusElement(event.cardSet).toSet()); } } + + Stream _mapCardSourceSelectedToState(CardSourceSelected event) async* { + if (event.isAllChecked == null || !event.isAllChecked) { + // Partial sets are selected, select all + yield state.copyWith( + selectedSets: state.selectedSets.plus( + state.cardSets.filter((cs) => cs.source == event.source) + ).toSet() + ); + } else if (event.isAllChecked) { + // All sets selected, select none + yield state.copyWith( + selectedSets: state.selectedSets.filter((s) => s.source != event.source).toSet() + ); + } + } + + Stream _mapChangePrizesToWinToState(ChangePrizesToWin event) async* { + AppPreferences().prizesToWin = event.prizesToWin; + yield state.copyWith(prizesToWin: event.prizesToWin); + } + + Stream _mapChangePlayerLimitToState(ChangePlayerLimit event) async* { + AppPreferences().playerLimit = event.playerLimit; + yield state.copyWith(playerLimit: event.playerLimit); + } + + Stream _mapChangePick2EnabledToState(ChangePick2Enabled event) async* { + yield state.copyWith(pick2Enabled: event.enabled); + } + + Stream _mapChangeDraw2Pick3EnabledToState(ChangeDraw2Pick3Enabled event) async* { + yield state.copyWith(draw2pick3Enabled: event.enabled); + } } diff --git a/lib/ui/creategame/bloc/create_game_event.dart b/lib/ui/creategame/bloc/create_game_event.dart index eeb902c..678aa3c 100644 --- a/lib/ui/creategame/bloc/create_game_event.dart +++ b/lib/ui/creategame/bloc/create_game_event.dart @@ -1,3 +1,4 @@ +import 'package:appsagainsthumanity/data/features/cards/model/card_set.dart'; import 'package:equatable/equatable.dart'; abstract class CreateGameEvent extends Equatable { @@ -10,10 +11,56 @@ abstract class CreateGameEvent extends Equatable { class ScreenLoaded extends CreateGameEvent {} class CardSetSelected extends CreateGameEvent { - final String cardSet; + final CardSet cardSet; CardSetSelected(this.cardSet); @override List get props => [cardSet]; } + +class CardSourceSelected extends CreateGameEvent { + final String source; + final bool isAllChecked; + + CardSourceSelected(this.source, this.isAllChecked); + + @override + List get props => [source, isAllChecked]; +} + +class ChangePrizesToWin extends CreateGameEvent { + final int prizesToWin; + + ChangePrizesToWin(this.prizesToWin); + + @override + List get props => [prizesToWin]; +} + +class ChangePlayerLimit extends CreateGameEvent { + final int playerLimit; + + ChangePlayerLimit(this.playerLimit); + + @override + List get props => [playerLimit]; +} + +class ChangePick2Enabled extends CreateGameEvent { + final bool enabled; + + ChangePick2Enabled(this.enabled); + + @override + List get props => [enabled]; +} + +class ChangeDraw2Pick3Enabled extends CreateGameEvent { + final bool enabled; + + ChangeDraw2Pick3Enabled(this.enabled); + + @override + List get props => [enabled]; +} diff --git a/lib/ui/creategame/bloc/create_game_state.dart b/lib/ui/creategame/bloc/create_game_state.dart index f3c9627..6a050bf 100644 --- a/lib/ui/creategame/bloc/create_game_state.dart +++ b/lib/ui/creategame/bloc/create_game_state.dart @@ -1,10 +1,17 @@ +import 'package:appsagainsthumanity/data/app_preferences.dart'; +import 'package:appsagainsthumanity/data/features/cards/model/card_set.dart'; import 'package:kt_dart/kt.dart'; import 'package:meta/meta.dart'; @immutable class CreateGameState { - final KtList cardSets; - final KtSet selectedSets; + final KtList cardSets; + final KtSet selectedSets; + final int prizesToWin; + final int playerLimit; + final bool pick2Enabled; + final bool draw2pick3Enabled; + final bool isLoading; final String error; @@ -13,25 +20,39 @@ class CreateGameState { @required this.selectedSets, @required this.isLoading, this.error, + this.prizesToWin = 7, + this.playerLimit = 15, + this.pick2Enabled = true, + this.draw2pick3Enabled = true }); - factory CreateGameState.loading({Set sets}) { + factory CreateGameState.empty() { return CreateGameState( cardSets: emptyList(), - selectedSets: sets.toImmutableSet() ?? emptySet(), + selectedSets: emptySet(), + prizesToWin: AppPreferences().prizesToWin, + playerLimit: AppPreferences().playerLimit, isLoading: true, ); } CreateGameState copyWith({ - KtList cardSets, - KtSet selectedSets, + KtList cardSets, + KtSet selectedSets, + int prizesToWin, + int playerLimit, + bool pick2Enabled, + bool draw2pick3Enabled, bool isLoading, String error, }) { return CreateGameState( cardSets: cardSets ?? this.cardSets, selectedSets: selectedSets ?? this.selectedSets, + prizesToWin: prizesToWin ?? this.prizesToWin, + playerLimit: playerLimit ?? this.playerLimit, + pick2Enabled: pick2Enabled ?? this.pick2Enabled, + draw2pick3Enabled: draw2pick3Enabled ?? this.draw2pick3Enabled, isLoading: isLoading ?? this.isLoading, error: error ?? this.error, ); @@ -41,7 +62,11 @@ class CreateGameState { String toString() { return '''CreateGameState { cardSets: $cardSets, - selectedSets: $selectedSets, + selectedSets: $selectedSets, + prizesToWin: $prizesToWin, + playerLimit: $playerLimit, + pick2Enabled: $pick2Enabled, + draw2pick3Enabled: $draw2pick3Enabled, isLoading: $isLoading, error: $error }'''; diff --git a/lib/ui/creategame/create_game_screen.dart b/lib/ui/creategame/create_game_screen.dart index c3e0ac2..ed7e5d3 100644 --- a/lib/ui/creategame/create_game_screen.dart +++ b/lib/ui/creategame/create_game_screen.dart @@ -1,17 +1,16 @@ +import 'package:appsagainsthumanity/data/features/cards/model/card_set.dart'; import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/ui/creategame/bloc/bloc.dart'; import 'package:appsagainsthumanity/internal.dart'; -import 'package:appsagainsthumanity/ui/game/game_screen.dart'; +import 'package:appsagainsthumanity/ui/creategame/widgets/CountPreference.dart'; import 'package:appsagainsthumanity/ui/routes.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kt_dart/kt.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:stepper_counter_swipe/stepper_counter_swipe.dart'; class CreateGameScreen extends StatefulWidget { - final Set cardSets; - - CreateGameScreen({Set sets}) : cardSets = sets ?? Set(); - @override State createState() => _CreateGameScreenState(); } @@ -21,7 +20,6 @@ class _CreateGameScreenState extends State { Widget build(BuildContext context) { return BlocProvider( create: (context) => CreateGameBloc( - widget.cardSets, context.repository(), context.repository(), )..add(ScreenLoaded()), @@ -45,56 +43,76 @@ class _CreateGameScreenState extends State { ); } }, builder: (context, state) { - return Scaffold( - appBar: AppBar( - title: Text(context.strings.titleNewGame), - leading: IconButton( - icon: Icon(Icons.arrow_back), - onPressed: () { - Navigator.of(context).pop(); - }, + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: AppBar( + title: Text(context.strings.titleNewGame), + backgroundColor: AppColors.surfaceDark, + leading: IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + bottom: TabBar( + tabs: [ + Tab(text: "CARDS"), + Tab(text: "OPTIONS"), + ], + ), ), - ), - bottomNavigationBar: BottomAppBar( - notchMargin: 8, - color: AppColors.primary, - shape: CircularNotchedRectangle(), - child: Container( - height: 56, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - "${state.selectedSets.size} Selected", - style: context.theme.textTheme.headline6, + bottomNavigationBar: BottomAppBar( + notchMargin: 8, + color: AppColors.surfaceDark, + shape: CircularNotchedRectangle(), + child: Container( + height: 56, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + "${state.selectedSets.size} Selected", + style: context.theme.textTheme.headline6, + ), ), ), - ), - ], + ], + ), ), ), - ), - floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, - floatingActionButton: FloatingActionButton( - child: Icon(Icons.play_arrow), - onPressed: state.selectedSets.isNotEmpty() - ? () async { - // Start game? - var newGame = await context.repository().createGame(state.selectedSets); - Navigator.of(context) - .pushReplacement(GamePageRoute(newGame)); - } + floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, + floatingActionButton: state.selectedSets.isNotEmpty() + ? FloatingActionButton( + child: Icon(Icons.check), + onPressed: () async { + // Start game? + var newGame = await context.repository().createGame( + state.selectedSets, + prizesToWin: state.prizesToWin, + playerLimit: state.playerLimit, + pick2Enabled: state.pick2Enabled, + draw2Pick3Enabled: state.draw2pick3Enabled, + ); + Navigator.of(context).pushReplacement(GamePageRoute(newGame)); + }, + ) : null, + body: TabBarView( + children: [ + _buildCardSetLists(state), + _buildGameOptions(context, state), + ], + ), ), - body: _buildBody(state), ); }); } - Widget _buildBody(CreateGameState state) { + Widget _buildCardSetLists(CreateGameState state) { return Column( children: [ Expanded( @@ -104,6 +122,51 @@ class _CreateGameScreenState extends State { ); } + Widget _buildGameOptions(BuildContext context, CreateGameState state) { + return Column( + children: [ + CountPreference( + state.prizesToWin, + title: "Prizes to win", + subtitle: "Choose the number of prize cards it would take to win", + min: 1, + max: 15, + onValueChanged: (value) { + context.bloc().add(ChangePrizesToWin(value)); + }, + ), + CountPreference( + state.playerLimit, + title: "Max # of players", + subtitle: "Pick the number of players allowed to join your game", + min: 5, + max: 30, + onValueChanged: (value) { + context.bloc().add(ChangePlayerLimit(value)); + }, + ), + SwitchListTile( + title: Text("Enable \"PICK 2\""), + subtitle: Text("Allow \"PICK 2\" prompt cards"), + activeColor: AppColors.primary, + value: state.pick2Enabled, + onChanged: (value) { + context.bloc().add(ChangePick2Enabled(value)); + }, + ), + SwitchListTile( + title: Text("Enable \"DRAW 2, PICK 3\""), + subtitle: Text("Allow \"DRAW 2, PICK 3\" prompt cards"), + activeColor: AppColors.primary, + value: state.draw2pick3Enabled, + onChanged: (value) { + context.bloc().add(ChangeDraw2Pick3Enabled(value)); + }, + ), + ], + ); + } + Widget _buildLoading() { return Container( height: double.maxFinite, @@ -112,26 +175,122 @@ class _CreateGameScreenState extends State { ); } - Widget _buildList(KtList sets, KtSet selected) { + Widget _buildList(KtList sets, KtSet selected) { + var groupedSets = sets.groupBy((cs) => cs.source); + print("Sets: $sets"); + print("Grouped Sets: $groupedSets"); + var widgets = groupedSets.keys.toList().sortedWith((a, b) { + var aW = keyWeight(a); + var bW = keyWeight(b); + if (aW.compareTo(bW) == 0) { + return a.compareTo(b); + } else { + return aW.compareTo(bW); + } + }).flatMap((key) { + var items = groupedSets.get(key).map((cs) => CardSetListItem(cs, selected.contains(cs))); + var allItemsSelected = items.all((i) => i.isSelected); + var noItemsSelected = items.none((i) => i.isSelected); + return mutableListOf( + HeaderItem(key, allItemsSelected ? true : noItemsSelected ? false : null), + )..addAll(items); + }); + return ListView.builder( - itemCount: sets.size, - itemBuilder: (context, index) { - var item = sets[index]; - var isSelected = selected.contains(item); - return ListTile( - title: Text(item), - contentPadding: const EdgeInsets.symmetric(horizontal: 6), - leading: Checkbox( - value: isSelected, - activeColor: AppColors.secondary, - onChanged: (value) { - context.bloc().add(CardSetSelected(item)); - }, + itemCount: widgets.size, + itemBuilder: (context, index) => widgets[index], + ); + } + + int keyWeight(String key) { + if (key == "Developer") { + return 0; + } else if (key == "CAH Main Deck") { + return 1; + } else if (key == "CAH Expansions") { + return 2; + } else if (key == "CAH Packs") { + return 3; + } else if (key.startsWith('CAH Packs/')) { + return 4; + } else { + return 5; + } + } +} + +class HeaderItem extends StatelessWidget { + final String title; + final bool isChecked; + + HeaderItem(this.title, [this.isChecked]); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + context.bloc().add(CardSourceSelected(title, isChecked)); + }, + child: Column( + children: [ + Divider( + height: 1, ), - onTap: () { - context.bloc().add(CardSetSelected(item)); - }, - ); + Container( + height: 48, + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(horizontal: 6), + child: Row( + children: [ + Checkbox( + value: isChecked, + tristate: true, + onChanged: (value) { + context.bloc().add(CardSourceSelected(title, value)); + }, + activeColor: AppColors.primary, + checkColor: Colors.white, + ), + Expanded( + child: Container( + margin: const EdgeInsets.only(left: 16), + child: Text( + title ?? "_UNKNOWN_", + style: context.theme.textTheme.subtitle2.copyWith( + color: AppColors.primaryVariant, + ), + ), + ), + ), + ], + ), + ) + ], + ), + ); + } +} + +class CardSetListItem extends StatelessWidget { + final CardSet cardSet; + final bool isSelected; + + CardSetListItem(this.cardSet, this.isSelected); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(cardSet.name), + contentPadding: const EdgeInsets.symmetric(horizontal: 6), + leading: Checkbox( + value: isSelected, + activeColor: AppColors.primary, + onChanged: (value) { + context.bloc().add(CardSetSelected(cardSet)); + }, + ), + onTap: () { + context.bloc().add(CardSetSelected(cardSet)); }, ); } diff --git a/lib/ui/creategame/widgets/CountPreference.dart b/lib/ui/creategame/widgets/CountPreference.dart new file mode 100644 index 0000000..a085d72 --- /dev/null +++ b/lib/ui/creategame/widgets/CountPreference.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:appsagainsthumanity/internal.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; + +class CountPreference extends StatelessWidget { + final String title; + final String subtitle; + final int value; + final int min, max; + final void Function(int) onValueChanged; + + CountPreference( + this.value, { + @required this.title, + this.subtitle, + @required this.onValueChanged, + this.min, + this.max, + }); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(title), + subtitle: subtitle != null ? Text(subtitle) : null, + contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + padding: const EdgeInsets.all(8), + icon: Icon( + MdiIcons.minus, + color: Colors.white, + ), + onPressed: () { + var newValue = (value - 1).clamp(min, max); + onValueChanged(newValue); + }, + ), + Container( + width: 36, + height: 36, + decoration: BoxDecoration(color: AppColors.primary, shape: BoxShape.circle), + alignment: Alignment.center, + child: Text( + value.toString(), + style: context.theme.textTheme.subtitle1.copyWith(color: Colors.white), + ), + ), + IconButton( + padding: const EdgeInsets.all(8), + icon: Icon( + MdiIcons.plus, + color: Colors.white, + ), + onPressed: () { + var newValue = (value + 1).clamp(min, max); + onValueChanged(newValue); + }, + ), + ], + ), + ); + } +} diff --git a/lib/ui/game/screens/complete/completed_game_screen.dart b/lib/ui/game/screens/complete/completed_game_screen.dart index faeab25..27dbf04 100644 --- a/lib/ui/game/screens/complete/completed_game_screen.dart +++ b/lib/ui/game/screens/complete/completed_game_screen.dart @@ -75,7 +75,7 @@ class CompletedGameScreen extends StatelessWidget { ), onPressed: () { Navigator.of(context).pushReplacement(MaterialPageRoute( - builder: (context) => CreateGameScreen(sets: state.game.cardSets,) + builder: (context) => CreateGameScreen() )); }, ), diff --git a/lib/ui/game/screens/gameplay/game_play_screen.dart b/lib/ui/game/screens/gameplay/game_play_screen.dart index cd1e9ff..4f6d33f 100644 --- a/lib/ui/game/screens/gameplay/game_play_screen.dart +++ b/lib/ui/game/screens/gameplay/game_play_screen.dart @@ -28,7 +28,7 @@ class _GamePlayScreenState extends State { bottomNavigationBar: BottomAppBar( notchMargin: 8, shape: CircularNotchedRectangle(), - color: AppColors.primary, + color: AppColors.surfaceLight, child: Container( height: 56, child: Row( @@ -37,7 +37,7 @@ class _GamePlayScreenState extends State { margin: const EdgeInsets.only(left: 8), child: IconButton( icon: Icon(Icons.close), - color: Colors.black87, + color: Colors.white, onPressed: () { Navigator.of(context).pop(); }, @@ -61,7 +61,7 @@ class _GamePlayScreenState extends State { ), IconButton( icon: Icon(MdiIcons.accountGroup), - color: Colors.black87, + color: Colors.white, onPressed: () { _showPlayerBottomSheet(context); }, @@ -147,7 +147,7 @@ class _GamePlayScreenState extends State { child: Text( widget.state.game.gid, style: context.theme.textTheme.headline6.copyWith( - color: AppColors.secondary, + color: AppColors.primaryVariant, ), ), ) diff --git a/lib/ui/game/screens/gameplay/widget/game_status_title.dart b/lib/ui/game/screens/gameplay/widget/game_status_title.dart index 8d5b042..939cf94 100644 --- a/lib/ui/game/screens/gameplay/widget/game_status_title.dart +++ b/lib/ui/game/screens/gameplay/widget/game_status_title.dart @@ -27,7 +27,7 @@ class GameStatusTitle extends StatelessWidget { Widget _buildText(BuildContext context, String title) { return Text( title, - style: context.theme.textTheme.headline6.copyWith(color: Colors.black87), + style: context.theme.textTheme.headline6.copyWith(color: Colors.white), ); } } diff --git a/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart b/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart index 8abd636..edea3a8 100644 --- a/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart +++ b/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart @@ -39,7 +39,7 @@ class JudgeBar extends StatelessWidget { trailing: IconButton( icon: Icon( hasDownvoted ? MdiIcons.thumbDown : MdiIcons.thumbDownOutline, - color: hasDownvoted ? AppColors.secondary : Colors.white, + color: hasDownvoted ? AppColors.primary : Colors.white, ), onPressed: () { context.bloc().add(DownvotePrompt()); diff --git a/lib/ui/game/screens/gameplay/widget/player_item.dart b/lib/ui/game/screens/gameplay/widget/player_item.dart index 5cc3968..dded4c9 100644 --- a/lib/ui/game/screens/gameplay/widget/player_item.dart +++ b/lib/ui/game/screens/gameplay/widget/player_item.dart @@ -28,7 +28,7 @@ class PlayerItem extends StatelessWidget { ? Text( "Judge", style: context.theme.textTheme.bodyText2.copyWith( - color: AppColors.secondary, + color: AppColors.primaryVariant, ), ) : null, @@ -42,7 +42,7 @@ class PlayerItem extends StatelessWidget { margin: const EdgeInsets.only(right: 16), child: Icon( MdiIcons.thumbDown, - color: AppColors.secondary, + color: AppColors.primaryVariant, ), ), Icon(MdiIcons.cardsPlayingOutline), diff --git a/lib/ui/game/screens/gameplay/widget/re_deal_button.dart b/lib/ui/game/screens/gameplay/widget/re_deal_button.dart index 94ed5b5..ba1b230 100644 --- a/lib/ui/game/screens/gameplay/widget/re_deal_button.dart +++ b/lib/ui/game/screens/gameplay/widget/re_deal_button.dart @@ -14,47 +14,44 @@ class ReDealButton extends StatelessWidget { return IconButton( icon: Icon(MdiIcons.cardsVariant), tooltip: "Re-deal your hand", - color: Colors.black87, + color: Colors.white, onPressed: () async { var result = await showDialog( - context: context, + context: context, builder: (context) { return AlertDialog( - title: Text('Deal new hand?'), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + title: Text('Deal new hand?'), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + content: RichText( + text: TextSpan(text: 'Spend ', children: [ + TextSpan( + text: '1 of ${state.currentPlayer.prizes.length} prize cards', + style: TextStyle(fontWeight: FontWeight.bold)), + TextSpan(text: ' to deal you a new hand?') + ]), + ), + actions: [ + FlatButton( + child: Text('CANCEL'), + textColor: AppColors.secondary, + onPressed: () { + Navigator.of(context).pop(false); + }, ), - content: RichText( - text: TextSpan( - text: 'Spend ', - children: [ - TextSpan(text: '1 of ${state.currentPlayer.prizes.length} prize cards', style: TextStyle(fontWeight: FontWeight.bold)), - TextSpan(text: ' to deal you a new hand?') - ] - ), + FlatButton( + child: Text('DEAL'), + textColor: AppColors.secondary, + onPressed: () { + Navigator.of(context).pop(true); + }, ), - actions: [ - FlatButton( - child: Text('CANCEL'), - textColor: AppColors.secondary, - onPressed: () { - Navigator.of(context).pop(false); - }, - ), - FlatButton( - child: Text('DEAL'), - textColor: AppColors.secondary, - onPressed: () { - Navigator.of(context).pop(true); - }, - ), - ], + ], ); - } - ); + }); if (result ?? false) { - await context.repository() - .reDealHand(state.game.id); + await context.repository().reDealHand(state.game.id); } }, ); diff --git a/lib/ui/game/screens/gameplay/widget/waiting_player_responses.dart b/lib/ui/game/screens/gameplay/widget/waiting_player_responses.dart index 0f5d5a3..ca49621 100644 --- a/lib/ui/game/screens/gameplay/widget/waiting_player_responses.dart +++ b/lib/ui/game/screens/gameplay/widget/waiting_player_responses.dart @@ -87,7 +87,7 @@ class PlayerResponseCard extends StatelessWidget { ) : Icon( MdiIcons.checkboxBlankCircleOutline, - color: AppColors.primary, + color: Colors.black54, size: 32, ), ), diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index 73d9991..e534212 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -62,18 +62,18 @@ class _HomeScreenState extends State { padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), child: Column( children: [ - if (state.games.isNotEmpty) - AspectRatio( - aspectRatio: 312 / 436, - child: PageView( - controller: _pageController, - children: [ - _buildTitleCard(context, state, includeMargin: false), + AspectRatio( + aspectRatio: 312 / 436, + child: PageView( + controller: _pageController, + children: [ + _buildTitleCard(context, state, includeMargin: false), + + if (state.games.isNotEmpty) _buildPastGamesCard(context, state), - ], - ), + ], ), - if (state.games.isEmpty) _buildTitleCard(context, state), + ), Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, @@ -203,7 +203,7 @@ class _HomeScreenState extends State { title: Text( game.gid, style: context.theme.textTheme.subtitle1 - .copyWith(color: AppColors.secondary, fontWeight: FontWeight.w500), + .copyWith(color: AppColors.primary, fontWeight: FontWeight.w500), ), subtitle: Text( game.state.label, diff --git a/lib/ui/widgets/player_circle_avatar.dart b/lib/ui/widgets/player_circle_avatar.dart index dfb0c02..c24dc5b 100644 --- a/lib/ui/widgets/player_circle_avatar.dart +++ b/lib/ui/widgets/player_circle_avatar.dart @@ -7,21 +7,17 @@ class PlayerCircleAvatar extends StatelessWidget { final double radius; String get playerInitials { - var splitName = player.name.split(' '); - if (splitName != null && splitName.isNotEmpty) { - var nonEmptyCharacters = splitName - .where((e) => e.isNotEmpty); - if (nonEmptyCharacters.isNotEmpty) { - return nonEmptyCharacters - .map((e) => e[0]) - .join() - .toUpperCase(); - } else { - return ""; - } + var splitName = player.name.split(' '); + if (splitName != null && splitName.isNotEmpty) { + var nonEmptyCharacters = splitName.where((e) => e.isNotEmpty); + if (nonEmptyCharacters.isNotEmpty) { + return nonEmptyCharacters.map((e) => e[0]).join().toUpperCase(); } else { - return ""; + return ""; } + } else { + return ""; + } } PlayerCircleAvatar({@required this.player, this.radius = 20}); @@ -39,7 +35,12 @@ class PlayerCircleAvatar extends StatelessWidget { backgroundColor: AppColors.primary, child: player.avatarUrl == null ? player.name != null - ? Text(playerInitials) + ? Text( + playerInitials, + style: context.theme.textTheme.subtitle1.copyWith( + color: Colors.white, + ), + ) : null : null, ); diff --git a/pubspec.lock b/pubspec.lock index 28b61e1..d28c56a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -775,6 +775,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.9.3" + stepper_counter_swipe: + dependency: "direct main" + description: + name: stepper_counter_swipe + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.5" stream_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c696ea0..7541fc1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,6 +62,7 @@ dependencies: timeago: ^2.0.26 webview_flutter: ^0.3.20+2 flutter_signin_button: ^1.0.0 + stepper_counter_swipe: ^0.0.5 json_annotation: ^3.0.0 From 423ae7c9aadc2e24528485089598a677a35e7ee9 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Sun, 26 Apr 2020 17:47:00 -0400 Subject: [PATCH 15/67] Attempting to fix CI workflow removing old dependencies Updating Build No. preparing for next release --- .github/workflows/CI.yml | 5 +++-- lib/ui/creategame/create_game_screen.dart | 2 -- pubspec.lock | 7 ------- pubspec.yaml | 6 ++---- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index cdd2d76..3166fd4 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,10 +11,11 @@ on: jobs: test: runs-on: ubuntu-latest - container: - image: google/dart:latest steps: - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: '12.x' - name: Install Flutter uses: subosito/flutter-action@v1 diff --git a/lib/ui/creategame/create_game_screen.dart b/lib/ui/creategame/create_game_screen.dart index ed7e5d3..ecc7091 100644 --- a/lib/ui/creategame/create_game_screen.dart +++ b/lib/ui/creategame/create_game_screen.dart @@ -7,8 +7,6 @@ import 'package:appsagainsthumanity/ui/routes.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kt_dart/kt.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:stepper_counter_swipe/stepper_counter_swipe.dart'; class CreateGameScreen extends StatefulWidget { @override diff --git a/pubspec.lock b/pubspec.lock index d28c56a..28b61e1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -775,13 +775,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.9.3" - stepper_counter_swipe: - dependency: "direct main" - description: - name: stepper_counter_swipe - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.5" stream_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7541fc1..b755cdb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+3 +version: 1.0.0+4 environment: sdk: ">=2.7.0 <3.0.0" @@ -36,6 +36,7 @@ dependencies: path: ^1.6.4 uuid: ^2.0.4 share: ^0.6.4 + json_annotation: ^3.0.0 # Architecture and Tools bloc: ^3.0.0 @@ -62,9 +63,6 @@ dependencies: timeago: ^2.0.26 webview_flutter: ^0.3.20+2 flutter_signin_button: ^1.0.0 - stepper_counter_swipe: ^0.0.5 - - json_annotation: ^3.0.0 dev_dependencies: flutter_test: From 4dbe90f4ae70588b019eb1e91468abdc5213e72c Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Sun, 26 Apr 2020 17:58:11 -0400 Subject: [PATCH 16/67] Attempting to fix dependencies slight color tweak --- ios/Podfile.lock | 21 +++++++++++++++++++ .../screens/gameplay/game_play_screen.dart | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4d9770b..9e3e3f0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -195,6 +195,9 @@ PODS: - FirebaseAnalytics (= 6.2.1) - Firebase/CoreOnly (6.15.0): - FirebaseCore (= 6.6.0) + - Firebase/DynamicLinks (6.15.0): + - Firebase/CoreOnly + - FirebaseDynamicLinks (~> 4.0.6) - Firebase/Firestore (6.15.0): - Firebase/CoreOnly - FirebaseFirestore (~> 1.9.0) @@ -222,6 +225,9 @@ PODS: - Flutter - firebase_core_web (0.1.0): - Flutter + - firebase_dynamic_links (0.1.0): + - Firebase/DynamicLinks + - Flutter - firebase_messaging (0.0.1): - Firebase/Core - Firebase/Messaging @@ -258,6 +264,9 @@ PODS: - GoogleUtilities/Logger (~> 6.5) - nanopb (~> 0.3.901) - FirebaseCoreDiagnosticsInterop (1.2.0) + - FirebaseDynamicLinks (4.0.8): + - FirebaseAnalyticsInterop (~> 1.3) + - FirebaseCore (~> 6.2) - FirebaseFirestore (1.9.0): - abseil/algorithm (= 0.20190808) - abseil/base (= 0.20190808) @@ -373,6 +382,8 @@ PODS: - Flutter - PromisesObjC (1.2.8) - Protobuf (3.11.4) + - share (0.0.1): + - Flutter - shared_preferences (0.0.1): - Flutter - shared_preferences_macos (0.0.1): @@ -394,6 +405,7 @@ DEPENDENCIES: - firebase_auth_web (from `.symlinks/plugins/firebase_auth_web/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_core_web (from `.symlinks/plugins/firebase_core_web/ios`) + - firebase_dynamic_links (from `.symlinks/plugins/firebase_dynamic_links/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - firebase_storage (from `.symlinks/plugins/firebase_storage/ios`) - Flutter (from `Flutter`) @@ -402,6 +414,7 @@ DEPENDENCIES: - package_info (from `.symlinks/plugins/package_info/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) + - share (from `.symlinks/plugins/share/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) @@ -420,6 +433,7 @@ SPEC REPOS: - FirebaseCore - FirebaseCoreDiagnostics - FirebaseCoreDiagnosticsInterop + - FirebaseDynamicLinks - FirebaseFirestore - FirebaseFunctions - FirebaseInstallations @@ -463,6 +477,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_core/ios" firebase_core_web: :path: ".symlinks/plugins/firebase_core_web/ios" + firebase_dynamic_links: + :path: ".symlinks/plugins/firebase_dynamic_links/ios" firebase_messaging: :path: ".symlinks/plugins/firebase_messaging/ios" firebase_storage: @@ -479,6 +495,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider/ios" path_provider_macos: :path: ".symlinks/plugins/path_provider_macos/ios" + share: + :path: ".symlinks/plugins/share/ios" shared_preferences: :path: ".symlinks/plugins/shared_preferences/ios" shared_preferences_macos: @@ -504,6 +522,7 @@ SPEC CHECKSUMS: firebase_auth_web: 0955c07bcc06e84af76b9d4e32e6f31518f2d7de firebase_core: dc539d4bdc69894823e4a6e557034a9e48960f21 firebase_core_web: d501d8b946b60c8af265428ce483b0fff5ad52d1 + firebase_dynamic_links: 7aaa8cbd086b55de330e34912dd144687e7d5c47 firebase_messaging: cffb57ce40958c6204f03fb0c81713e4cd1e240c firebase_storage: 5e931af5cdef32331676c659bdd1ebcaba9dc78a FirebaseAnalytics: e83e64b1231dedcd9ddd4bdecd9bcfd6ba341679 @@ -513,6 +532,7 @@ SPEC CHECKSUMS: FirebaseCore: 4aeb81ff53dcd9a3634ca725dc1fb8c2a4622046 FirebaseCoreDiagnostics: 5e78803ab276bc5b50340e3c539c06c3de35c649 FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850 + FirebaseDynamicLinks: 417dc6dbb6013233c77558290d73296f429656a6 FirebaseFirestore: b7e6adda31974dbd259fc25b541e8850420c92ed FirebaseFunctions: 5af7c35d1c5e41608fecbb667eb6c4e672e318d0 FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b @@ -538,6 +558,7 @@ SPEC CHECKSUMS: path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 + share: 0b2c3e82132f5888bccca3351c504d0003b3b410 shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 diff --git a/lib/ui/game/screens/gameplay/game_play_screen.dart b/lib/ui/game/screens/gameplay/game_play_screen.dart index 4f6d33f..7a47662 100644 --- a/lib/ui/game/screens/gameplay/game_play_screen.dart +++ b/lib/ui/game/screens/gameplay/game_play_screen.dart @@ -28,7 +28,7 @@ class _GamePlayScreenState extends State { bottomNavigationBar: BottomAppBar( notchMargin: 8, shape: CircularNotchedRectangle(), - color: AppColors.surfaceLight, + color: AppColors.surfaceDark, child: Container( height: 56, child: Row( From 15aa998ec95383e73a94eca01d83a8b33bd4b331 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Mon, 27 Apr 2020 18:19:08 -0400 Subject: [PATCH 17/67] #14 - Adjusting theme and coloring (working on a fully dark mode) #6 - this is in process and I believe that the function-backend is ready for it --- ios/Flutter/.last_build_id | 2 +- ios/Runner.xcodeproj/project.pbxproj | 3 - ios/Runner/Info.plist | 2 + .../game/firestore_game_repository.dart | 121 ++++++--------- lib/data/features/game/game_repository.dart | 3 + lib/data/features/users/model/user_game.dart | 9 +- lib/internal/theme.dart | 15 +- .../gameplay/widget/game_status_title.dart | 4 +- .../gameplay/widget/judge/judge_dredd.dart | 18 +-- .../widget/player_response_picker.dart | 14 +- lib/ui/home/home_screen.dart | 77 +--------- lib/ui/home/widgets/past_game_card.dart | 140 ++++++++++++++++++ lib/ui/settings/settings_screen.dart | 21 +-- .../settings/widgets/preference_header.dart | 2 +- pubspec.lock | 37 ++--- pubspec.yaml | 2 +- 16 files changed, 260 insertions(+), 210 deletions(-) create mode 100644 lib/ui/home/widgets/past_game_card.dart diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id index 06c5caa..9a89407 100644 --- a/ios/Flutter/.last_build_id +++ b/ios/Flutter/.last_build_id @@ -1 +1 @@ -5f8e23ba106f05148b26d487766e9033 \ No newline at end of file +5fc5ace65c1e79eb804479a1704677d5 \ No newline at end of file diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 32e4980..33de715 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -332,7 +332,6 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -417,7 +416,6 @@ }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -473,7 +471,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 1972f42..01b238c 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable diff --git a/lib/data/features/game/firestore_game_repository.dart b/lib/data/features/game/firestore_game_repository.dart index 30e72ff..f60f75f 100644 --- a/lib/data/features/game/firestore_game_repository.dart +++ b/lib/data/features/game/firestore_game_repository.dart @@ -45,7 +45,7 @@ class FirestoreGameRepository extends GameRepository { await newGameDoc.setData(game.toJson()); // Now add yourself as a player to the game - await _addSelfToGame(newGameDoc.documentID, game); + await _addSelfToGame(gameDocumentId: newGameDoc.documentID); return game; }); @@ -54,23 +54,7 @@ class FirestoreGameRepository extends GameRepository { @override Future joinGame(String gid) { return currentUserOrThrow((firebaseUser) async { - var snapshot = - await db.collection(FirebaseConstants.COLLECTION_GAMES).where('gid', isEqualTo: gid).limit(1).getDocuments(); - - if (snapshot != null && snapshot.documents.isNotEmpty) { - var gameDocument = snapshot.documents.first; - var game = Game.fromDocument(gameDocument); - - // Only let user's join a game that hasn't been started yet - if (game.state == GameState.waitingRoom) { - await _addSelfToGame(gameDocument.documentID, game); - return game; - } else { - throw 'You cannot join a game that is already in-progress or completed'; - } - } else { - throw 'No game found for $gid'; - } + return await _addSelfToGame(gid: gid); }); } @@ -94,9 +78,12 @@ class FirestoreGameRepository extends GameRepository { var snapshot = await gameDocument.get(); var game = Game.fromDocument(snapshot); - // TODO: Update this to be joinable at anypoint in hte game if the game is setup for it - if (andJoin && game.state == GameState.waitingRoom) { - await _addSelfToGame(gameDocumentId, game); + if (andJoin) { + try { + game = await _addSelfToGame(gameDocumentId: gameDocumentId); + } catch (e, st) { + print("Error joining game: $e\n$st"); + } } else if (andJoin) { throw 'You cannot join a game that has already started'; } @@ -104,6 +91,19 @@ class FirestoreGameRepository extends GameRepository { return game; } + @override + Future leaveGame(String gameDocumentId) { + return currentUserOrThrow((firebaseUser) async { + // Flag player object as inactive in the game + + // remove from judging order + + // remove any responses + + // Delete UserGame obj + }); + } + @override Stream> observeJoinedGames() { return streamCurrentUserOrThrow((firebaseUser) { @@ -171,34 +171,17 @@ class FirestoreGameRepository extends GameRepository { Future startGame(String gameDocumentId) async { final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(functionName: 'startGame'); dynamic response = await callable.call({'game_id': gameDocumentId}); - print("Start game Successful! $response"); } @override - Future submitResponse(String gameDocumentId, List cards) { - // 1. Remove the cards from the player's hand - // 2. Add the cards as a response to the current game's turn - return currentUserOrThrow((firebaseUser) async { - var gameDocument = db.collection(FirebaseConstants.COLLECTION_GAMES).document(gameDocumentId); - - var playerDocument = gameDocument.collection(FirebaseConstants.COLLECTION_PLAYERS).document(firebaseUser.uid); - - var cardData = cards.map((e) => e.toJson()).toList(); - - await db.runTransaction((transaction) async { - var playerSnapshot = await transaction.get(playerDocument); - var player = Player.fromDocument(playerSnapshot); - - player.hand.removeWhere((element) { - return cards.firstWhere((c) => c.cid == element.cid, orElse: () => null) != null; - }); - - await transaction.update(playerDocument, {'hand': player.hand.map((e) => e.toJson()).toList()}); - - await transaction.update(gameDocument, {'turn.responses.${firebaseUser.uid}': cardData}); - }); + Future submitResponse(String gameDocumentId, List cards) async { + final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(functionName: 'submitResponses'); + dynamic response = await callable.call({ + 'game_id': gameDocumentId, + 'responses': cards.map((e) => e.cid).toList() }); + print("Responses Submitted! $response"); } @override @@ -236,46 +219,28 @@ class FirestoreGameRepository extends GameRepository { Future pickWinner(String gameDocumentId, String playerId) async { final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(functionName: 'pickWinner'); dynamic response = await callable.call({'game_id': gameDocumentId, 'player_id': playerId}); - print("Winner picked Successful! $response"); } - Future _addSelfToGame(String gameDocumentId, Game game) async { - // first check if game CAN be joined - var limit = game.playerLimit ?? Game.PLAYER_LIMIT; - var players = await db - .collection(FirebaseConstants.COLLECTION_GAMES) - .document(game.id) - .collection(FirebaseConstants.COLLECTION_PLAYERS) - .getDocuments(); + Future _addSelfToGame({String gameDocumentId, String gid}) async { + var user = await userRepository.getUser(); - if (players.documents.length >= limit) { - throw 'Unable to join. This game is already full'; - } + final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(functionName: 'joinGame'); + HttpsCallableResult response = await callable.call({ + if (gameDocumentId != null) + 'game_id': gameDocumentId, - var user = await userRepository.getUser(); - var player = Player(id: user.id, name: user.name, avatarUrl: user.avatarUrl); + if (gid != null) + 'gid': gid, - // Write self to game's players - await db - .collection(FirebaseConstants.COLLECTION_GAMES) - .document(gameDocumentId) - .collection(FirebaseConstants.COLLECTION_PLAYERS) - .document(user.id) - .setData(player.toJson(), merge: true); - - // Write game to your collection of games - var userGame = UserGame( - gid: game.gid, - state: game.state, - joinedAt: DateTime.now(), - ); - await db - .collection(FirebaseConstants.COLLECTION_USERS) - .document(user.id) - .collection(FirebaseConstants.COLLECTION_GAMES) - .document(gameDocumentId) - .setData(userGame.toJson()); + 'name': user.name, + 'avatar': user.avatarUrl + }); + + var jsonResponse = Map.from(response.data); + var game = Game.fromJson(jsonResponse); + game.id = jsonResponse['id']; + return game; } String generateId({int length = 7}) { diff --git a/lib/data/features/game/game_repository.dart b/lib/data/features/game/game_repository.dart index 66c02e3..8f71bcf 100644 --- a/lib/data/features/game/game_repository.dart +++ b/lib/data/features/game/game_repository.dart @@ -27,6 +27,9 @@ abstract class GameRepository { /// Get a game by it's actual document id Future getGame(String gameDocumentId, {bool andJoin = false}); + /// Leave a game. This will flag the 'player' on the game as 'inActive' + Future leaveGame(String gameDocumentId); + /// Return a list of games that you have joined in the past Stream> observeJoinedGames(); diff --git a/lib/data/features/users/model/user_game.dart b/lib/data/features/users/model/user_game.dart index f850c0a..784ccf1 100644 --- a/lib/data/features/users/model/user_game.dart +++ b/lib/data/features/users/model/user_game.dart @@ -7,17 +7,24 @@ part 'user_game.g.dart'; @JsonSerializable() class UserGame { + @JsonKey(ignore: true) + String id; final String gid; final GameState state; final DateTime joinedAt; UserGame({ + this.id, @required this.gid, @required this.state, @required this.joinedAt, }); - factory UserGame.fromDocument(DocumentSnapshot document) => UserGame.fromJson(document.data); + factory UserGame.fromDocument(DocumentSnapshot document) { + var game = UserGame.fromJson(document.data); + game.id = document.documentID; + return game; + } factory UserGame.fromJson(Map json) => _$UserGameFromJson(json); diff --git a/lib/internal/theme.dart b/lib/internal/theme.dart index 5a1fec3..f4d4fc3 100644 --- a/lib/internal/theme.dart +++ b/lib/internal/theme.dart @@ -6,6 +6,8 @@ class AppColors { static const primary = Color(0xFFAB47BC); static const primaryDark = Color(0xFF790e8b); static const primaryVariant = Color(0xFFdf78ef); + static const colorOnPrimary = Color(0xFFFFFFFF); + static const colorOnPrimaryVariant = Color(0xDD000000); static const secondary = Color(0xFF00BCD4); static const secondaryLight = Color(0xFF80DEEA); @@ -27,7 +29,18 @@ class AppThemes { primaryColorDark: AppColors.primaryDark, primaryColorLight: AppColors.primaryVariant, accentColor: AppColors.primary, - canvasColor: AppColors.surface + canvasColor: AppColors.surface, + cardColor: Colors.white + ); + + static ThemeData get dark => + ThemeData( + brightness: Brightness.dark, + primaryColor: AppColors.primary, + primaryColorDark: AppColors.primaryDark, + primaryColorLight: AppColors.primaryVariant, + accentColor: AppColors.primaryVariant, + cardColor: AppColors.surfaceLight ); } diff --git a/lib/ui/game/screens/gameplay/widget/game_status_title.dart b/lib/ui/game/screens/gameplay/widget/game_status_title.dart index 939cf94..5db9ccf 100644 --- a/lib/ui/game/screens/gameplay/widget/game_status_title.dart +++ b/lib/ui/game/screens/gameplay/widget/game_status_title.dart @@ -10,11 +10,11 @@ class GameStatusTitle extends StatelessWidget { if (state.areWeJudge) { return _buildText( context, - !state.allResponsesSubmitted ? "Waiting for responses" : "Judge them! You are the Law!", + !state.allResponsesSubmitted ? "Waiting for responses" : "Judging", ); } else { if (state.allResponsesSubmitted) { - return _buildText(context, "Judgement day is upon you!"); + return _buildText(context, "Waiting for judgement!"); } else if (state.haveWeSubmittedResponse) { return _buildText(context, "Waiting on other players"); } else { diff --git a/lib/ui/game/screens/gameplay/widget/judge/judge_dredd.dart b/lib/ui/game/screens/gameplay/widget/judge/judge_dredd.dart index 694aa70..b43609a 100644 --- a/lib/ui/game/screens/gameplay/widget/judge/judge_dredd.dart +++ b/lib/ui/game/screens/gameplay/widget/judge/judge_dredd.dart @@ -76,7 +76,7 @@ class _JudgeDreddState extends State { return RaisedButton.icon( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), shape: StadiumBorder(), - color: AppColors.secondary, + color: AppColors.primary, onPressed: () async { var currentPlayerResponse = controller.currentPlayerResponse; if (currentPlayerResponse != null) { @@ -87,14 +87,14 @@ class _JudgeDreddState extends State { }, icon: Icon( MdiIcons.crown, - color: Colors.black87, + color: AppColors.colorOnPrimary, ), label: Container( margin: const EdgeInsets.only(left: 16, right: 40), child: Text( "WINNER", style: context.theme.textTheme.button.copyWith( - color: Colors.black87, + color: AppColors.colorOnPrimary, letterSpacing: 1, ), ), @@ -106,13 +106,13 @@ class _JudgeDreddState extends State { return RaisedButton.icon( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), shape: StadiumBorder(), - color: AppColors.secondary, - disabledColor: AppColors.secondary, + color: AppColors.primary, + disabledColor: AppColors.primary, icon: Container( width: 24, height: 24, child: CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(AppColors.primary), + valueColor: AlwaysStoppedAnimation(AppColors.colorOnPrimary), ), ), label: Container( @@ -120,7 +120,7 @@ class _JudgeDreddState extends State { child: Text( "SUBMITTING...", style: context.theme.textTheme.button.copyWith( - color: Colors.black87, + color: AppColors.colorOnPrimary, letterSpacing: 1, ), ), @@ -141,7 +141,7 @@ class _JudgeDreddState extends State { height: 48, width: 56, child: Material( - color: AppColors.secondary, + color: AppColors.primary, clipBehavior: Clip.hardEdge, shape: RoundedRectangleBorder( borderRadius: isLeft @@ -168,7 +168,7 @@ class _JudgeDreddState extends State { alignment: Alignment.center, child: Icon( iconData, - color: Colors.black87, + color: AppColors.colorOnPrimary, ), ), ), diff --git a/lib/ui/game/screens/gameplay/widget/player_response_picker.dart b/lib/ui/game/screens/gameplay/widget/player_response_picker.dart index 2328593..f96f5da 100644 --- a/lib/ui/game/screens/gameplay/widget/player_response_picker.dart +++ b/lib/ui/game/screens/gameplay/widget/player_response_picker.dart @@ -68,14 +68,14 @@ class _PlayerResponsePickerState extends State { return RaisedButton.icon( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), shape: StadiumBorder(), - color: AppColors.secondary, - disabledColor: AppColors.secondary, + color: AppColors.primary, + disabledColor: AppColors.primary, onPressed: null, icon: Container( width: 24, height: 24, child: CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(AppColors.primary), + valueColor: AlwaysStoppedAnimation(AppColors.colorOnPrimary), ), ), label: Container( @@ -83,7 +83,7 @@ class _PlayerResponsePickerState extends State { child: Text( "SUBMITTING...", style: context.theme.textTheme.button.copyWith( - color: Colors.black87, + color: AppColors.colorOnPrimary, letterSpacing: 1, ), ), @@ -95,20 +95,20 @@ class _PlayerResponsePickerState extends State { return RaisedButton.icon( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), shape: StadiumBorder(), - color: AppColors.secondary, + color: AppColors.primary, onPressed: () async { context.bloc().add(SubmitResponses()); }, icon: Icon( MdiIcons.uploadMultiple, - color: Colors.black87, + color: AppColors.colorOnPrimary, ), label: Container( margin: const EdgeInsets.only(left: 8, right: 20), child: Text( "SUBMIT RESPONSE", style: context.theme.textTheme.button.copyWith( - color: Colors.black87, + color: AppColors.colorOnPrimary, letterSpacing: 1, ), ), diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index e534212..2e92f03 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -9,6 +9,7 @@ import 'package:appsagainsthumanity/ui/creategame/create_game_screen.dart'; import 'package:appsagainsthumanity/ui/game/game_screen.dart'; import 'package:appsagainsthumanity/ui/home/bloc/bloc.dart'; import 'package:appsagainsthumanity/ui/home/widgets/join_room_dialog.dart'; +import 'package:appsagainsthumanity/ui/home/widgets/past_game_card.dart'; import 'package:appsagainsthumanity/ui/routes.dart'; import 'package:appsagainsthumanity/ui/settings/settings_screen.dart'; import 'package:flutter/material.dart'; @@ -70,7 +71,7 @@ class _HomeScreenState extends State { _buildTitleCard(context, state, includeMargin: false), if (state.games.isNotEmpty) - _buildPastGamesCard(context, state), + PastGamesCard(state), ], ), ), @@ -122,7 +123,7 @@ class _HomeScreenState extends State { child: Material( elevation: 4.0, borderRadius: BorderRadius.circular(16), - color: Colors.white, + color: context.theme.cardColor, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -167,78 +168,6 @@ class _HomeScreenState extends State { ); } - Widget _buildPastGamesCard(BuildContext context, HomeState state) { - var topMargin = MediaQuery.of(context).padding.top + (Platform.isAndroid ? 24 : 0); - return Container( - margin: EdgeInsets.only(left: 8, right: 8, top: topMargin), - child: AspectRatio( - aspectRatio: 312 / 436, - child: Material( - elevation: 4.0, - borderRadius: BorderRadius.circular(16), - color: Colors.white, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Container( - margin: const EdgeInsets.all(24), - child: Text( - "Past Games", - style: context.theme.textTheme.headline3 - .copyWith(color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 48), - ), - ), - Divider( - height: 1, - color: Colors.black12, - ), - Expanded( - child: ListView.builder( - padding: const EdgeInsets.symmetric(vertical: 8), - itemCount: state.games.length, - itemBuilder: (context, index) { - var game = state.games[index]; - return ListTile( - title: Text( - game.gid, - style: context.theme.textTheme.subtitle1 - .copyWith(color: AppColors.primary, fontWeight: FontWeight.w500), - ), - subtitle: Text( - game.state.label, - style: context.theme.textTheme.bodyText2.copyWith(color: Colors.black54), - ), - trailing: Text( - timeago.format(game.joinedAt), - style: context.theme.textTheme.bodyText2.copyWith(color: Colors.black26), - ), - onTap: game.state == GameState.inProgress || game.state == GameState.waitingRoom - ? () async { - // Fetch and load game - try { - var existingGame = await context.repository().findGame(game.gid); - Navigator.of(context) - .push(GamePageRoute(existingGame)); - } catch (e) { - Scaffold.of(context) - ..hideCurrentSnackBar() - ..showSnackBar(SnackBar( - content: Text("$e"), - )); - } - } - : null, - ); - }), - ) - ], - ), - ), - ), - ); - } - Widget _buildMenuAction({ @required BuildContext context, @required IconData icon, diff --git a/lib/ui/home/widgets/past_game_card.dart b/lib/ui/home/widgets/past_game_card.dart new file mode 100644 index 0000000..5458a56 --- /dev/null +++ b/lib/ui/home/widgets/past_game_card.dart @@ -0,0 +1,140 @@ +import 'dart:io'; +import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; +import 'package:appsagainsthumanity/data/features/game/model/game_state.dart'; +import 'package:appsagainsthumanity/data/features/users/model/user_game.dart'; +import 'package:appsagainsthumanity/ui/home/bloc/bloc.dart'; +import 'package:appsagainsthumanity/internal.dart'; +import 'package:appsagainsthumanity/ui/routes.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:timeago/timeago.dart' as timeago; + +class PastGamesCard extends StatelessWidget { + final HomeState state; + + PastGamesCard(this.state); + + @override + Widget build(BuildContext context) { + var topMargin = MediaQuery.of(context).padding.top + (Platform.isAndroid ? 24 : 0); + return Container( + margin: EdgeInsets.only(left: 8, right: 8, top: topMargin), + child: AspectRatio( + aspectRatio: 312 / 436, + child: Material( + elevation: 4.0, + borderRadius: BorderRadius.circular(16), + color: Colors.white, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Container( + margin: const EdgeInsets.all(24), + child: Text( + "Past Games", + style: context.theme.textTheme.headline3 + .copyWith(color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 48), + ), + ), + Divider( + height: 1, + color: Colors.black12, + ), + Expanded( + child: ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 8), + itemCount: state.games.length, + itemBuilder: (context, index) { + var game = state.games[index]; + return PastGame(game); + }), + ) + ], + ), + ), + ), + ); + } + + void openGame(BuildContext context, String gid) async { + // Fetch and load game + try { + var existingGame = await context.repository().findGame(gid); + Navigator.of(context).push(GamePageRoute(existingGame)); + } catch (e) { + Scaffold.of(context) + ..hideCurrentSnackBar() + ..showSnackBar(SnackBar( + content: Text("$e"), + )); + } + } +} + +class PastGame extends StatelessWidget { + final UserGame game; + + PastGame(this.game); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text( + game.gid, + style: context.theme.textTheme.subtitle1.copyWith(color: AppColors.primary, fontWeight: FontWeight.w500), + ), + subtitle: Text( + game.state.label, + style: context.theme.textTheme.bodyText2.copyWith(color: Colors.black54), + ), + trailing: Text( + timeago.format(game.joinedAt), + style: context.theme.textTheme.bodyText2.copyWith(color: Colors.black26), + ), + onTap: game.state == GameState.inProgress || game.state == GameState.waitingRoom ? () => openGame(context) : null, + onLongPress: () => leaveGame(context), + ); + } + + void openGame(BuildContext context) async { + try { + var existingGame = await context.repository().getGame(game.id); + Navigator.of(context).push(GamePageRoute(existingGame)); + } catch (e) { + Scaffold.of(context) + ..hideCurrentSnackBar() + ..showSnackBar(SnackBar( + content: Text("$e"), + )); + } + } + + void leaveGame(BuildContext context) async { + var result = await showDialog( + context: context, + builder: (context) { + return AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + title: Text("Leave game?"), + content: Text("Are you sure you want to leave the game ${game.gid}?"), + actions: [ + FlatButton( + child: Text("CANCEL"), + textColor: AppColors.primaryVariant, + onPressed: () => Navigator.of(context).pop(false), + ), + FlatButton( + child: Text("LEAVE"), + textColor: AppColors.primaryVariant, + onPressed: () => Navigator.of(context).pop(true), + ), + ], + ); + }); + + if (result == true) { + await context.repository().leaveGame(game.id); + } + } +} diff --git a/lib/ui/settings/settings_screen.dart b/lib/ui/settings/settings_screen.dart index 0549500..75ee790 100644 --- a/lib/ui/settings/settings_screen.dart +++ b/lib/ui/settings/settings_screen.dart @@ -27,26 +27,26 @@ class SettingsScreen extends StatelessWidget { children: [ UserPreference(), Preference( - title: "Delete account", - titleColor: Colors.redAccent, + title: "Sign out", icon: Icon( - MdiIcons.deleteForeverOutline, - color: Colors.redAccent, + MdiIcons.logout, + color: Colors.black54, ), onTap: () { - _deleteAccount(context); + _signOut(context); }, ), Preference( - title: "Sign out", + title: "Delete account", + titleColor: Colors.redAccent, icon: Icon( - MdiIcons.logout, - color: Colors.black54, + MdiIcons.deleteForeverOutline, + color: Colors.redAccent, ), onTap: () { - _signOut(context); + _deleteAccount(context); }, - ) + ), ], ), PreferenceCategory( @@ -156,6 +156,7 @@ class SettingsScreen extends StatelessWidget { context: context, builder: (context) { return AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Text( "Delete account?", style: context.theme.textTheme.headline6.copyWith(color: Colors.redAccent), diff --git a/lib/ui/settings/widgets/preference_header.dart b/lib/ui/settings/widgets/preference_header.dart index 6800ccb..ef936e8 100644 --- a/lib/ui/settings/widgets/preference_header.dart +++ b/lib/ui/settings/widgets/preference_header.dart @@ -17,7 +17,7 @@ class PreferenceHeader extends StatelessWidget { alignment: Alignment.centerLeft, child: Text( title, - style: context.theme.textTheme.subtitle2.copyWith(color: context.primaryColor), + style: context.theme.textTheme.subtitle2.copyWith(color: AppColors.primary), ), ); } diff --git a/pubspec.lock b/pubspec.lock index 28b61e1..1c5780f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,13 +113,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "7.0.9" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.1" charcode: dependency: transitive description: @@ -134,6 +127,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" cloud_firestore: dependency: "direct main" description: @@ -225,13 +225,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.4" - dartx: - dependency: "direct main" - description: - name: dartx - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.0" device_info: dependency: "direct main" description: @@ -246,6 +239,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" firebase: dependency: transitive description: @@ -580,7 +580,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" + version: "1.7.0" path_provider: dependency: "direct main" description: @@ -824,13 +824,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.3" - time: - dependency: transitive - description: - name: time - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" timeago: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index b755cdb..8467acc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,7 +42,7 @@ dependencies: bloc: ^3.0.0 flutter_bloc: ^3.2.0 rxdart: ^0.23.1 - dartx: ^0.3.0 + #dartx: ^0.3.0 kt_dart: ^0.7.0+1 equatable: ^1.1.1 From ab0ec93e00dfe253383788afbeab36d46be28961 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Mon, 27 Apr 2020 18:19:52 -0400 Subject: [PATCH 18/67] =?UTF-8?q?minor=20refactoring=20of=20pubspec=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pubspec.yaml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 8467acc..e7f2ab6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,16 +5,6 @@ description: A CaH application for both iOS and Android # pub.dev using `pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html version: 1.0.0+4 environment: From ddb8fa70f2c9e88a268208f2a8bb67f5d055e661 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Mon, 27 Apr 2020 18:22:15 -0400 Subject: [PATCH 19/67] updated release notes for next version --- android/distribution/release-notes.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/android/distribution/release-notes.txt b/android/distribution/release-notes.txt index 7519586..f1a1e0c 100644 --- a/android/distribution/release-notes.txt +++ b/android/distribution/release-notes.txt @@ -1,5 +1,6 @@ -* Fixed Prompt Macros -* Added Push Notifications -* Fixed how the player list can scroll -* Fixed some end of game functions -* Optimizations and improvements for the app +* Most of game logic is now offloaded to the backend +* Permissions are now setup on DB to prevent nefarious access +* Improved 'New Game' interface +* Added dynamic link (share link to friends to install/join) this may or may not work fully yet until release +* Improving look & feel with new color scheme +* Fixed how the player list scrolling reacts From 94b7ebaccec543308a222349b8e4270c1276f3db Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Tue, 28 Apr 2020 12:17:03 -0400 Subject: [PATCH 20/67] =?UTF-8?q?Resolve=20#8=20-=20Added=20profile=20edit?= =?UTF-8?q?or=20that=20let's=20you=20change=20your=20name=20and=20avatar?= =?UTF-8?q?=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/distribution/groups-alpha.txt | 1 + android/distribution/release-notes.txt | 12 +- appdistribution.sh | 20 +++ ios/Flutter/.last_build_id | 2 +- ios/Podfile.lock | 12 ++ ios/Runner/Info.plist | 4 + lib/data/features/users/user_repository.dart | 63 +++++++- lib/internal/theme.dart | 3 + lib/ui/home/bloc/home_bloc.dart | 13 +- lib/ui/home/bloc/home_event.dart | 11 ++ lib/ui/home/home_screen.dart | 11 +- lib/ui/profile/bloc/bloc.dart | 3 + lib/ui/profile/bloc/profile_bloc.dart | 79 ++++++++++ lib/ui/profile/bloc/profile_event.dart | 46 ++++++ lib/ui/profile/bloc/profile_state.dart | 32 ++++ lib/ui/profile/profile_screen.dart | 137 +++++++++++++++++ lib/ui/profile/widgets/profile_photo.dart | 150 +++++++++++++++++++ lib/ui/routes.dart | 9 +- lib/ui/settings/settings_screen.dart | 9 +- lib/ui/settings/widgets/user_preference.dart | 24 +-- pubspec.lock | 14 ++ pubspec.yaml | 1 + regenerate.sh | 1 + 23 files changed, 625 insertions(+), 32 deletions(-) create mode 100644 android/distribution/groups-alpha.txt create mode 100755 appdistribution.sh create mode 100644 lib/ui/profile/bloc/bloc.dart create mode 100644 lib/ui/profile/bloc/profile_bloc.dart create mode 100644 lib/ui/profile/bloc/profile_event.dart create mode 100644 lib/ui/profile/bloc/profile_state.dart create mode 100644 lib/ui/profile/profile_screen.dart create mode 100644 lib/ui/profile/widgets/profile_photo.dart diff --git a/android/distribution/groups-alpha.txt b/android/distribution/groups-alpha.txt new file mode 100644 index 0000000..6e10cb5 --- /dev/null +++ b/android/distribution/groups-alpha.txt @@ -0,0 +1 @@ +52inc,friends-&-family diff --git a/android/distribution/release-notes.txt b/android/distribution/release-notes.txt index f1a1e0c..71b3685 100644 --- a/android/distribution/release-notes.txt +++ b/android/distribution/release-notes.txt @@ -1,6 +1,6 @@ -* Most of game logic is now offloaded to the backend -* Permissions are now setup on DB to prevent nefarious access -* Improved 'New Game' interface -* Added dynamic link (share link to friends to install/join) this may or may not work fully yet until release -* Improving look & feel with new color scheme -* Fixed how the player list scrolling reacts +• Most of game logic is now offloaded to the backend +• Permissions are now setup on DB to prevent nefarious access +• Improved 'New Game' interface +• Added dynamic link (share link to friends to install/join) this may or may not work fully yet until release +• Improving look & feel with new color scheme +• Fixed how the player list scrolling reacts diff --git a/appdistribution.sh b/appdistribution.sh new file mode 100755 index 0000000..8d8d0ee --- /dev/null +++ b/appdistribution.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +if [ "$1" == --help ] +then + echo "Usage: ./appdistribution.sh [--build]" +else + + if [[ "$3" == --build ]] + then + flutter clean + flutter build apk + fi + + firebase appdistribution:distribute build/app/outputs/apk/release/app-release.apk \ + --app 1:100898335413:android:88a8670f87c5c1ea8cacfc \ + --token "$1" \ + --release-notes-file android/distribution/release-notes.txt \ + --groups-file "$2" \ + +fi diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id index 9a89407..6e6317c 100644 --- a/ios/Flutter/.last_build_id +++ b/ios/Flutter/.last_build_id @@ -1 +1 @@ -5fc5ace65c1e79eb804479a1704677d5 \ No newline at end of file +51f973fefc248ab6ddfc36e9ba31956a \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9e3e3f0..7ef8c0f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -307,6 +307,8 @@ PODS: - FirebaseCore (~> 6.0) - GTMSessionFetcher/Core (~> 1.1) - Flutter (1.0.0) + - flutter_plugin_android_lifecycle (0.0.1): + - Flutter - google_sign_in (0.0.1): - Flutter - GoogleSignIn (~> 5.0) @@ -368,6 +370,8 @@ PODS: - GTMSessionFetcher/Core (1.3.1) - GTMSessionFetcher/Full (1.3.1): - GTMSessionFetcher/Core (= 1.3.1) + - image_picker (0.0.1): + - Flutter - leveldb-library (1.22) - nanopb (0.3.9011): - nanopb/decode (= 0.3.9011) @@ -409,8 +413,10 @@ DEPENDENCIES: - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - firebase_storage (from `.symlinks/plugins/firebase_storage/ios`) - Flutter (from `Flutter`) + - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) - google_sign_in (from `.symlinks/plugins/google_sign_in/ios`) - google_sign_in_web (from `.symlinks/plugins/google_sign_in_web/ios`) + - image_picker (from `.symlinks/plugins/image_picker/ios`) - package_info (from `.symlinks/plugins/package_info/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) @@ -485,10 +491,14 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_storage/ios" Flutter: :path: Flutter + flutter_plugin_android_lifecycle: + :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" google_sign_in: :path: ".symlinks/plugins/google_sign_in/ios" google_sign_in_web: :path: ".symlinks/plugins/google_sign_in_web/ios" + image_picker: + :path: ".symlinks/plugins/image_picker/ios" package_info: :path: ".symlinks/plugins/package_info/ios" path_provider: @@ -540,6 +550,7 @@ SPEC CHECKSUMS: FirebaseMessaging: 9e23f9d603b1fb71ccbeb48d79bf0d4ba0272c84 FirebaseStorage: 6c5263796af3b1be82ed173598aade47535fe125 Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + flutter_plugin_android_lifecycle: dc0b544e129eebb77a6bfb1239d4d1c673a60a35 google_sign_in: f32920a589fdf4ab2918ec6dc5e5b0d5b8040ff5 google_sign_in_web: 52deb24929ac0992baff65c57956031c44ed44c3 GoogleAppMeasurement: a08a43b8677b95ed51fcef880e36737334d804fd @@ -551,6 +562,7 @@ SPEC CHECKSUMS: gRPC-Core: c9aef9a261a1247e881b18059b84d597293c9947 GTMAppAuth: 4deac854479704f348309e7b66189e604cf5e01e GTMSessionFetcher: cea130bbfe5a7edc8d06d3f0d17288c32ffe9925 + image_picker: 66aa71bc96850a90590a35d4c4a2907b0d823109 leveldb-library: 55d93ee664b4007aac644a782d11da33fba316f7 nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd package_info: 48b108e75b8802c2d5e126f208ef540561c98aef diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 01b238c..19672a0 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,10 @@ + NSCameraUsageDescription + Used to change your profile photo + NSPhotoLibraryUsageDescription + Used to change your profile photo ITSAppUsesNonExemptEncryption CFBundleDevelopmentRegion diff --git a/lib/data/features/users/user_repository.dart b/lib/data/features/users/user_repository.dart index 7751f38..ca6346b 100644 --- a/lib/data/features/users/user_repository.dart +++ b/lib/data/features/users/user_repository.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:apple_sign_in/apple_sign_in.dart'; import 'package:appsagainsthumanity/data/app_preferences.dart'; import 'package:appsagainsthumanity/data/features/game/model/player.dart'; @@ -6,14 +8,14 @@ import 'package:appsagainsthumanity/data/firestore.dart'; import 'package:appsagainsthumanity/internal/push.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_storage/firebase_storage.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:logging/logging.dart'; class UserRepository { - final FirebaseAuth _auth; + final FirebaseAuth _auth = FirebaseAuth.instance; final Firestore _db = Firestore.instance; - - UserRepository({FirebaseAuth firebaseAuth}) : _auth = firebaseAuth ?? FirebaseAuth.instance; + final FirebaseStorage _storage = FirebaseStorage.instance; Future isSignedIn() async { return (await _auth.currentUser()) != null; @@ -31,6 +33,51 @@ class UserRepository { }); } + Stream observeUser() { + return streamCurrentUserOrThrow((firebaseUser) { + var doc = _userDocument(firebaseUser); + return doc.snapshots().map((snapshot) { + return User( + id: snapshot.documentID, + name: snapshot['name'], + avatarUrl: snapshot['avatarUrl'], + updatedAt: (snapshot['updatedAt'] as Timestamp).toDate(), + ); + }); + }); + } + + Future updateDisplayName(String name) { + return currentUserOrThrow((firebaseUser) async { + await _userDocument(firebaseUser).updateData({ + 'name': name + }); + }); + } + + Future deleteProfilePhoto() { + return currentUserOrThrow((firebaseUser) async { + var ref = _profilePhotoReference(firebaseUser); + await ref.delete(); + + await _userDocument(firebaseUser).updateData({ + 'avatarUrl': null + }); + }); + } + + Future updateProfilePhoto(File image) { + return currentUserOrThrow((firebaseUser) async { + var ref = _profilePhotoReference(firebaseUser); + var refSnapshot = await ref.putFile(image).onComplete; + var downloadUrl = await refSnapshot.ref.getDownloadURL(); + + await _userDocument(firebaseUser).updateData({ + 'avatarUrl': downloadUrl.toString() + }); + }); + } + Future signInWithGoogle() async { GoogleSignIn _googleSignIn = GoogleSignIn( scopes: [ @@ -144,9 +191,17 @@ class UserRepository { await AppPreferences().clear(); var user = await _auth.currentUser(); await user.delete(); + await _profilePhotoReference(user).delete(); } DocumentReference _userDocument(FirebaseUser user) { - return _db.collection(FirebaseConstants.COLLECTION_USERS).document(user.uid); + return _db.collection(FirebaseConstants.COLLECTION_USERS) + .document(user.uid); + } + + StorageReference _profilePhotoReference(FirebaseUser user) { + return _storage.ref() + .child(FirebaseConstants.COLLECTION_USERS) + .child(user.uid); } } diff --git a/lib/internal/theme.dart b/lib/internal/theme.dart index f4d4fc3..219233e 100644 --- a/lib/internal/theme.dart +++ b/lib/internal/theme.dart @@ -17,6 +17,9 @@ class AppColors { static const surfaceLight = Color(0xFF4F4F4F); static const surfaceDark = Color(0xFF303030); static const responseCardBackground = Color(0xFFF2F2F2); + + static const addPhotoBackground = Color(0xFF666666); + static const addPhotoForeground = Colors.white70; } class AppThemes { diff --git a/lib/ui/home/bloc/home_bloc.dart b/lib/ui/home/bloc/home_bloc.dart index 0212d28..41d1237 100644 --- a/lib/ui/home/bloc/home_bloc.dart +++ b/lib/ui/home/bloc/home_bloc.dart @@ -10,6 +10,7 @@ import 'package:logging/logging.dart'; class HomeBloc extends Bloc { UserRepository _userRepository; GameRepository _gameRepository; + StreamSubscription _userSubscription; StreamSubscription _joinedGamesSubscription; HomeBloc(this._userRepository, this._gameRepository); @@ -23,13 +24,17 @@ class HomeBloc extends Bloc { yield* _mapHomeStartedToState(); } else if (event is JoinedGamesUpdated){ yield* _mapJoinedGamesUpdatedToState(event); + } else if (event is UserUpdated) { + yield* _mapUserUpdatedToState(event); } } Stream _mapHomeStartedToState() async* { try { - var user = await _userRepository.getUser(); - yield state.copyWith(isLoading: false, user: user); + _userSubscription?.cancel(); + _userSubscription = _userRepository.observeUser().listen((user) { + add(UserUpdated(user)); + }); _joinedGamesSubscription?.cancel(); _joinedGamesSubscription = _gameRepository.observeJoinedGames().listen((event) { @@ -44,4 +49,8 @@ class HomeBloc extends Bloc { Stream _mapJoinedGamesUpdatedToState(JoinedGamesUpdated event) async* { yield state.copyWith(games: event.games..sort((a, b) => b.joinedAt.compareTo(a.joinedAt))); } + + Stream _mapUserUpdatedToState(UserUpdated event) async* { + yield state.copyWith(user: event.user, isLoading: false); + } } diff --git a/lib/ui/home/bloc/home_event.dart b/lib/ui/home/bloc/home_event.dart index 5a98e88..7c535b4 100644 --- a/lib/ui/home/bloc/home_event.dart +++ b/lib/ui/home/bloc/home_event.dart @@ -1,3 +1,4 @@ +import 'package:appsagainsthumanity/data/features/users/model/user.dart'; import 'package:appsagainsthumanity/data/features/users/model/user_game.dart'; import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; @@ -20,3 +21,13 @@ class JoinedGamesUpdated extends HomeEvent { @override List get props => [games]; } + +@immutable +class UserUpdated extends HomeEvent { + final User user; + + UserUpdated(this.user); + + @override + List get props => [user]; +} diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index 2e92f03..004bef2 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -10,6 +10,7 @@ import 'package:appsagainsthumanity/ui/game/game_screen.dart'; import 'package:appsagainsthumanity/ui/home/bloc/bloc.dart'; import 'package:appsagainsthumanity/ui/home/widgets/join_room_dialog.dart'; import 'package:appsagainsthumanity/ui/home/widgets/past_game_card.dart'; +import 'package:appsagainsthumanity/ui/profile/profile_screen.dart'; import 'package:appsagainsthumanity/ui/routes.dart'; import 'package:appsagainsthumanity/ui/settings/settings_screen.dart'; import 'package:flutter/material.dart'; @@ -124,6 +125,7 @@ class _HomeScreenState extends State { elevation: 4.0, borderRadius: BorderRadius.circular(16), color: context.theme.cardColor, + clipBehavior: Clip.hardEdge, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -158,7 +160,7 @@ class _HomeScreenState extends State { child: Container( child: state.isLoading ? _buildLoadingUserTile() - : state.error != null ? _buildErrorUserTile(state.error) : _buildUserTile(state.user)), + : state.error != null ? _buildErrorUserTile(state.error) : _buildUserTile(context, state.user)), ), ) ], @@ -243,8 +245,13 @@ class _HomeScreenState extends State { ); } - Widget _buildUserTile(User user) { + Widget _buildUserTile(BuildContext context, User user) { return ListTile( + onTap: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => ProfileScreen() + )); + }, contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), title: Text( user.name ?? Player.DEFAULT_NAME, diff --git a/lib/ui/profile/bloc/bloc.dart b/lib/ui/profile/bloc/bloc.dart new file mode 100644 index 0000000..631354e --- /dev/null +++ b/lib/ui/profile/bloc/bloc.dart @@ -0,0 +1,3 @@ +export 'profile_bloc.dart'; +export 'profile_state.dart'; +export 'profile_event.dart'; diff --git a/lib/ui/profile/bloc/profile_bloc.dart b/lib/ui/profile/bloc/profile_bloc.dart new file mode 100644 index 0000000..dd011e6 --- /dev/null +++ b/lib/ui/profile/bloc/profile_bloc.dart @@ -0,0 +1,79 @@ +import 'dart:async'; + +import 'package:appsagainsthumanity/data/features/users/user_repository.dart'; +import 'package:appsagainsthumanity/ui/profile/bloc/profile_event.dart'; +import 'package:appsagainsthumanity/ui/profile/bloc/profile_state.dart'; +import 'package:bloc/bloc.dart'; + +class ProfileBloc extends Bloc { + final UserRepository _userRepository; + + StreamSubscription _userSubscription; + + ProfileBloc(this._userRepository); + + @override + ProfileState get initialState => ProfileState(); + + @override + Future close() async { + super.close(); + await _userSubscription?.cancel(); + } + + @override + Stream mapEventToState(ProfileEvent event) async* { + if (event is ScreenLoaded) { + yield* _mapScreenLoadedToState(); + } else if (event is UserLoaded) { + yield* _mapUserLoadedToState(event); + } else if (event is DisplayNameChanged) { + yield* _mapDisplayNameChangedToState(event); + } else if (event is PhotoChanged) { + yield* _mapPhotoChangedToState(event); + } else if (event is DeleteProfilePhoto) { + yield* _mapDeletePhotoToState(); + } + } + + Stream _mapScreenLoadedToState() async* { + _userSubscription?.cancel(); + _userSubscription = _userRepository.observeUser().listen((user) { + add(UserLoaded(user)); + }); + } + + Stream _mapUserLoadedToState(UserLoaded event) async* { + yield state.copyWith(user: event.user, isLoading: false, error: null); + } + + Stream _mapDisplayNameChangedToState(DisplayNameChanged event) async* { + yield state.copyWith(isLoading: true, error: null); + try { + await _userRepository.updateDisplayName(event.name); + yield state.copyWith(isLoading: false); + } catch (e) { + yield state.copyWith(isLoading: false, error: e.toString()); + } + } + + Stream _mapPhotoChangedToState(PhotoChanged event) async* { + yield state.copyWith(isLoading: true, error: null); + try { + await _userRepository.updateProfilePhoto(event.file); + yield state.copyWith(isLoading: false); + } catch (e) { + yield state.copyWith(isLoading: false, error: e.toString()); + } + } + + Stream _mapDeletePhotoToState() async* { + yield state.copyWith(isLoading: true, error: null); + try { + await _userRepository.deleteProfilePhoto(); + yield state.copyWith(isLoading: false); + } catch (e) { + yield state.copyWith(isLoading: false, error: e.toString()); + } + } +} diff --git a/lib/ui/profile/bloc/profile_event.dart b/lib/ui/profile/bloc/profile_event.dart new file mode 100644 index 0000000..77fb779 --- /dev/null +++ b/lib/ui/profile/bloc/profile_event.dart @@ -0,0 +1,46 @@ +import 'dart:io'; + +import 'package:appsagainsthumanity/data/features/users/model/user.dart'; +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +abstract class ProfileEvent extends Equatable { + const ProfileEvent(); + + @override + List get props => throw UnimplementedError(); +} + +class ScreenLoaded extends ProfileEvent {} + +@immutable +class UserLoaded extends ProfileEvent { + final User user; + + UserLoaded(this.user); + + @override + List get props => [user]; +} + +@immutable +class DisplayNameChanged extends ProfileEvent { + final String name; + + DisplayNameChanged(this.name); + + @override + List get props => [name]; +} + +@immutable +class PhotoChanged extends ProfileEvent { + final File file; + + PhotoChanged(this.file); + + @override + List get props => [file]; +} + +class DeleteProfilePhoto extends ProfileEvent {} diff --git a/lib/ui/profile/bloc/profile_state.dart b/lib/ui/profile/bloc/profile_state.dart new file mode 100644 index 0000000..2765e3c --- /dev/null +++ b/lib/ui/profile/bloc/profile_state.dart @@ -0,0 +1,32 @@ +import 'package:appsagainsthumanity/data/features/users/model/user.dart'; +import 'package:meta/meta.dart'; + +@immutable +class ProfileState { + final bool isLoading; + final String error; + final User user; + + ProfileState({ + this.isLoading = true, + this.error, + this.user, + }); + + ProfileState copyWith({ + bool isLoading, + String error, + User user, + }) { + return ProfileState( + isLoading: isLoading ?? this.isLoading, + error: error ?? this.error, + user: user ?? this.user, + ); + } + + @override + String toString() { + return 'ProfileState{isLoading: $isLoading, error: $error, user: $user}'; + } +} diff --git a/lib/ui/profile/profile_screen.dart b/lib/ui/profile/profile_screen.dart new file mode 100644 index 0000000..7c0a78f --- /dev/null +++ b/lib/ui/profile/profile_screen.dart @@ -0,0 +1,137 @@ +import 'package:appsagainsthumanity/internal.dart'; +import 'package:appsagainsthumanity/ui/profile/bloc/bloc.dart'; +import 'package:appsagainsthumanity/ui/profile/widgets/profile_photo.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; + +class ProfileScreen extends StatefulWidget { + @override + _ProfileScreenState createState() => _ProfileScreenState(); +} + +class _ProfileScreenState extends State { + final TextEditingController _displayNameController = TextEditingController(); + + @override + void dispose() { + _displayNameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => ProfileBloc(context.repository())..add(ScreenLoaded()), + child: _buildBody(), + ); + } + + Widget _buildBody() { + return BlocConsumer( + listenWhen: (previous, current) => current.user?.name != previous.user?.name, + listener: (context, state) { + _displayNameController.text = state.user?.name; + }, + builder: (context, state) { + return Scaffold( + appBar: AppBar( + title: Text("Profile"), + backgroundColor: AppColors.surfaceDark, + ), + body: Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 56), + child: Stack( + alignment: Alignment.center, + children: [ + ProfilePhoto( + state.user, + size: 196, + ), + if (state.isLoading) CircularProgressIndicator(), + if (!state.isLoading && state.user?.avatarUrl != null) _buildEditPhotoStack(context) + ], + ), + ), + Container( + margin: const EdgeInsets.only(left: 16, right: 16, bottom: 16, top: 16), + child: ListTile( + title: Text( + "User ID", + style: context.theme.textTheme.subtitle1.copyWith( + color: AppColors.primaryVariant, + ), + ), + subtitle: Text(state.user?.id ?? "Loading..."), + ), + ), + Container( + margin: const EdgeInsets.symmetric(horizontal: 32), + child: TextFormField( + controller: _displayNameController, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: "Display name", + ), + keyboardType: TextInputType.text, + textInputAction: TextInputAction.go, + onFieldSubmitted: (value) { + context.bloc().add(DisplayNameChanged(value)); + }, + ), + ) + ], + ), + ); + }, + ); + } + + Widget _buildEditPhotoStack(BuildContext context) { + return IgnorePointer( + ignoring: true, + child: Container( + margin: const EdgeInsets.only(top: 72), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + MdiIcons.imageEditOutline, + color: Colors.white, + ), + Container( + margin: const EdgeInsets.only(left: 8), + child: Text( + "EDIT", + style: context.theme.textTheme.subtitle1.copyWith( + fontWeight: FontWeight.w700, + color: Colors.white, + ), + ), + ) + ], + ), + ), + ); + } + + String getUserInitials(String name) { + if (name != null) { + var splitName = name.split(' '); + if (splitName != null && splitName.isNotEmpty) { + var nonEmptyCharacters = splitName.where((e) => e.isNotEmpty); + if (nonEmptyCharacters.isNotEmpty) { + return nonEmptyCharacters.map((e) => e[0]).join().toUpperCase(); + } else { + return ""; + } + } else { + return ""; + } + } + return ""; + } +} diff --git a/lib/ui/profile/widgets/profile_photo.dart b/lib/ui/profile/widgets/profile_photo.dart new file mode 100644 index 0000000..557e2ef --- /dev/null +++ b/lib/ui/profile/widgets/profile_photo.dart @@ -0,0 +1,150 @@ +import 'package:appsagainsthumanity/data/features/users/model/user.dart'; +import 'package:appsagainsthumanity/internal.dart'; +import 'package:appsagainsthumanity/ui/profile/bloc/bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:image_picker/image_picker.dart'; + +class ProfilePhoto extends StatelessWidget { + final User user; + final double size; + + ProfilePhoto(this.user, {this.size = 156}); + + @override + Widget build(BuildContext context) { + var profilePhotoUrl = user?.avatarUrl; + return profilePhotoUrl != null + ? _buildExistingPhotoAction(context, profilePhotoUrl) + : _buildAddPhotoAction(context); + } + + Widget _buildAddPhotoAction(BuildContext context) { + return Material( + shape: CircleBorder(), + clipBehavior: Clip.hardEdge, + color: AppColors.addPhotoBackground, + child: InkWell( + splashColor: AppColors.primary.withOpacity(0.30), + highlightColor: AppColors.primary.withOpacity(0.30), + onTap: () => _onProfilePhotoClicked(context), + child: SizedBox( + width: size, + height: size, + child: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.add_a_photo, + size: 46.0, + color: AppColors.addPhotoForeground, + ), + + Container( + margin: const EdgeInsets.only(top: 4), + child: Text( + "Add photo", + style: Theme.of(context).textTheme.button.copyWith(color: AppColors.addPhotoForeground), + ), + ) + ], + ), + ), + ), + ), + ); + } + + Widget _buildExistingPhotoAction(BuildContext context, String avatarUrl) { + return Material( + elevation: 4.0, + shape: CircleBorder(), + clipBehavior: Clip.hardEdge, + color: Colors.transparent, + child: Ink.image( + image: NetworkImage(avatarUrl), + fit: BoxFit.cover, + width: size, + height: size, + child: InkWell( + onTap: () => _onProfilePhotoClicked(context), + ), + ), + ); + } + + void _onProfilePhotoClicked(BuildContext context) async { + var action = await _showPhotoBottomSheet(context); + print("Result: $action"); + if (action is DeletePhoto) { + context.bloc() + .add(DeleteProfilePhoto()); + } else if (action is UpdatePhoto) { + var image = await ImagePicker.pickImage(source: action.source); + if (image != null) { + context.bloc() + .add(PhotoChanged(image)); + } + } + } + + Future _showPhotoBottomSheet(BuildContext context) async { + return await showModalBottomSheet( + context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ) + ), + backgroundColor: context.theme.canvasColor, + builder: (context) { + return Container( + child: Wrap( + children: [ + Container( + padding: EdgeInsets.all(16), + child: Text( + "Update your profile photo", + style: context.theme.textTheme.bodyText1.copyWith(color: Colors.white70), + ), + ), + ListTile( + title: Text("Choose from gallery"), + leading: Icon(Icons.image), + onTap: () { + Navigator.of(context).pop(UpdatePhoto(ImageSource.gallery)); + }, + ), + ListTile( + title: Text("Use camera"), + leading: Icon(Icons.camera_alt), + onTap: () { + Navigator.of(context).pop(UpdatePhoto(ImageSource.camera)); + }, + ), + ListTile( + title: Text("Delete photo"), + leading: Icon(Icons.delete), + onTap: () { + Navigator.of(context).pop(DeletePhoto()); + }, + ) + ], + ), + ); + }); + } +} + +abstract class ProfilePhotoAction {} + +class DeletePhoto extends ProfilePhotoAction {} + +class UpdatePhoto extends ProfilePhotoAction { + final ImageSource source; + + UpdatePhoto(this.source); +} diff --git a/lib/ui/routes.dart b/lib/ui/routes.dart index 9ca5e44..fac4b5a 100644 --- a/lib/ui/routes.dart +++ b/lib/ui/routes.dart @@ -36,28 +36,29 @@ class NoAnimationPageRoute extends MaterialPageRoute { class RouteTracer extends NavigatorObserver { Route currentRoute; + bool logEnabled = false; @override void didPop(Route route, Route previousRoute) { currentRoute = previousRoute; - print("Route Changed => $currentRoute"); + if (logEnabled) print("Route Changed => $currentRoute"); } @override void didReplace({Route newRoute, Route oldRoute}) { currentRoute = newRoute; - print("Route Changed => $currentRoute"); + if (logEnabled) print("Route Changed => $currentRoute"); } @override void didRemove(Route route, Route previousRoute) { currentRoute = previousRoute; - print("Route Changed => $currentRoute"); + if (logEnabled) print("Route Changed => $currentRoute"); } @override void didPush(Route route, Route previousRoute) { currentRoute = route; - print("Route Changed => $currentRoute"); + if (logEnabled) print("Route Changed => $currentRoute"); } } diff --git a/lib/ui/settings/settings_screen.dart b/lib/ui/settings/settings_screen.dart index 75ee790..092da72 100644 --- a/lib/ui/settings/settings_screen.dart +++ b/lib/ui/settings/settings_screen.dart @@ -1,6 +1,7 @@ import 'package:appsagainsthumanity/authentication_bloc/authentication_bloc.dart'; import 'package:appsagainsthumanity/data/features/users/user_repository.dart'; import 'package:appsagainsthumanity/internal.dart'; +import 'package:appsagainsthumanity/ui/profile/profile_screen.dart'; import 'package:appsagainsthumanity/ui/settings/widgets/preference.dart'; import 'package:appsagainsthumanity/ui/settings/widgets/preference_header.dart'; import 'package:appsagainsthumanity/ui/settings/widgets/user_preference.dart'; @@ -25,7 +26,13 @@ class SettingsScreen extends StatelessWidget { PreferenceCategory( title: "Account", children: [ - UserPreference(), + UserPreference( + onTap: (user) { + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => ProfileScreen() + )); + }, + ), Preference( title: "Sign out", icon: Icon( diff --git a/lib/ui/settings/widgets/user_preference.dart b/lib/ui/settings/widgets/user_preference.dart index b86f679..d9f9af1 100644 --- a/lib/ui/settings/widgets/user_preference.dart +++ b/lib/ui/settings/widgets/user_preference.dart @@ -1,32 +1,32 @@ +import 'package:appsagainsthumanity/data/features/users/model/user.dart'; +import 'package:appsagainsthumanity/data/features/users/user_repository.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kt_dart/kt.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:appsagainsthumanity/internal.dart'; class UserPreference extends StatelessWidget { - final void Function(UserInfo) onTap; + final void Function(User) onTap; UserPreference({this.onTap}); @override Widget build(BuildContext context) { - return StreamBuilder( - stream: FirebaseAuth.instance.currentUser().asStream(), + return StreamBuilder( + stream: context.repository().observeUser(), builder: (context, snapshot) { return _buildPreference(context, snapshot?.data); }, ); } - Widget _buildPreference(BuildContext context, @nullable UserInfo user) { - if (user != null) { - print("${user.displayName}, ${user.uid}, ${user.providerId}, ${user.photoUrl}"); - } + Widget _buildPreference(BuildContext context, @nullable User user) { return ListTile( leading: _avatar(user), title: Text( - user?.displayName != null ? "${user.displayName}" : "Loading...", + user?.name != null ? "${user.name}" : "Loading...", style: context.theme.textTheme.subtitle1.copyWith( color: Colors.black87, ), @@ -35,11 +35,11 @@ class UserPreference extends StatelessWidget { ); } - Widget _avatar(@nullable UserInfo user) { + Widget _avatar(@nullable User user) { return CircleAvatar( - child: user?.photoUrl != null ? null : Icon(MdiIcons.account, color: Colors.black87,), - backgroundColor: user?.photoUrl != null ? Colors.black12 : AppColors.primary, - backgroundImage: user?.photoUrl != null ? NetworkImage(user.photoUrl) : null, + child: user?.avatarUrl != null ? null : Icon(MdiIcons.account, color: Colors.black87,), + backgroundColor: user?.avatarUrl != null ? Colors.black12 : AppColors.primary, + backgroundImage: user?.avatarUrl != null ? NetworkImage(user.avatarUrl) : null, radius: 20, ); } diff --git a/pubspec.lock b/pubspec.lock index 1c5780f..3617dff 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -347,6 +347,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.7" flutter_signin_button: dependency: "direct main" description: @@ -434,6 +441,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.4" + image_picker: + dependency: "direct main" + description: + name: image_picker + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.5+2" intl: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index e7f2ab6..58e482a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,7 @@ dependencies: timeago: ^2.0.26 webview_flutter: ^0.3.20+2 flutter_signin_button: ^1.0.0 + image_picker: ^0.6.5+2 dev_dependencies: flutter_test: diff --git a/regenerate.sh b/regenerate.sh index 2615866..c69aa07 100755 --- a/regenerate.sh +++ b/regenerate.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash +flutter pub get flutter generate flutter packages pub run build_runner build --delete-conflicting-outputs From 4358ab5c9d605aa04f534c347a6e2548c26bc212 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Tue, 28 Apr 2020 18:40:25 -0400 Subject: [PATCH 21/67] Added update timestamp when updating user obj --- lib/data/features/users/user_repository.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/data/features/users/user_repository.dart b/lib/data/features/users/user_repository.dart index ca6346b..79e3eda 100644 --- a/lib/data/features/users/user_repository.dart +++ b/lib/data/features/users/user_repository.dart @@ -50,7 +50,8 @@ class UserRepository { Future updateDisplayName(String name) { return currentUserOrThrow((firebaseUser) async { await _userDocument(firebaseUser).updateData({ - 'name': name + 'name': name, + 'updatedAt': Timestamp.now() }); }); } @@ -61,7 +62,8 @@ class UserRepository { await ref.delete(); await _userDocument(firebaseUser).updateData({ - 'avatarUrl': null + 'avatarUrl': null, + 'updatedAt': Timestamp.now() }); }); } @@ -73,7 +75,8 @@ class UserRepository { var downloadUrl = await refSnapshot.ref.getDownloadURL(); await _userDocument(firebaseUser).updateData({ - 'avatarUrl': downloadUrl.toString() + 'avatarUrl': downloadUrl.toString(), + 'updatedAt': Timestamp.now() }); }); } From 6c83b6fa2dde615014dd50036cffc98cfab23143 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Tue, 28 Apr 2020 19:12:51 -0400 Subject: [PATCH 22/67] Preparing for next release --- android/distribution/release-notes.txt | 7 +------ pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/android/distribution/release-notes.txt b/android/distribution/release-notes.txt index 71b3685..a5257df 100644 --- a/android/distribution/release-notes.txt +++ b/android/distribution/release-notes.txt @@ -1,6 +1 @@ -• Most of game logic is now offloaded to the backend -• Permissions are now setup on DB to prevent nefarious access -• Improved 'New Game' interface -• Added dynamic link (share link to friends to install/join) this may or may not work fully yet until release -• Improving look & feel with new color scheme -• Fixed how the player list scrolling reacts +• Added profile editor so you can now change your name and/or avatar diff --git a/pubspec.yaml b/pubspec.yaml index 58e482a..b3568e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ description: A CaH application for both iOS and Android # pub.dev using `pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.0+4 +version: 1.0.0+5 environment: sdk: ">=2.7.0 <3.0.0" From 8d46374fd920409598cda75128298e57a71b0dfc Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Wed, 29 Apr 2020 10:41:45 -0400 Subject: [PATCH 23/67] Resolve #6 - Added ability to leave a game, and thusly remove it from the list Added loading and state indications for joining a game and leaving a game Fixed issue with profile edit screen on small screens --- ios/Flutter/.last_build_id | 2 +- .../game/firestore_game_repository.dart | 12 +- lib/ui/home/bloc/home_bloc.dart | 24 ++++ lib/ui/home/bloc/home_event.dart | 20 +++ lib/ui/home/bloc/home_state.dart | 19 ++- lib/ui/home/home_screen.dart | 121 ++++++++++-------- lib/ui/home/widgets/past_game_card.dart | 20 +-- lib/ui/profile/profile_screen.dart | 12 +- 8 files changed, 154 insertions(+), 76 deletions(-) diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id index 6e6317c..d0a06ff 100644 --- a/ios/Flutter/.last_build_id +++ b/ios/Flutter/.last_build_id @@ -1 +1 @@ -51f973fefc248ab6ddfc36e9ba31956a \ No newline at end of file +a1123ba8b4e706d507163ebadfdf15dd \ No newline at end of file diff --git a/lib/data/features/game/firestore_game_repository.dart b/lib/data/features/game/firestore_game_repository.dart index f60f75f..04ce020 100644 --- a/lib/data/features/game/firestore_game_repository.dart +++ b/lib/data/features/game/firestore_game_repository.dart @@ -94,13 +94,11 @@ class FirestoreGameRepository extends GameRepository { @override Future leaveGame(String gameDocumentId) { return currentUserOrThrow((firebaseUser) async { - // Flag player object as inactive in the game - - // remove from judging order - - // remove any responses - - // Delete UserGame obj + final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(functionName: 'leaveGame'); + HttpsCallableResult response = await callable.call({ + 'game_id': gameDocumentId + }); + print("Game left! ${response.data}"); }); } diff --git a/lib/ui/home/bloc/home_bloc.dart b/lib/ui/home/bloc/home_bloc.dart index 41d1237..3e6a101 100644 --- a/lib/ui/home/bloc/home_bloc.dart +++ b/lib/ui/home/bloc/home_bloc.dart @@ -26,6 +26,10 @@ class HomeBloc extends Bloc { yield* _mapJoinedGamesUpdatedToState(event); } else if (event is UserUpdated) { yield* _mapUserUpdatedToState(event); + } else if (event is LeaveGame) { + yield* _mapLeaveGameToState(event); + } else if (event is JoinGame) { + yield* _mapJoinGameToState(event); } } @@ -53,4 +57,24 @@ class HomeBloc extends Bloc { Stream _mapUserUpdatedToState(UserUpdated event) async* { yield state.copyWith(user: event.user, isLoading: false); } + + Stream _mapLeaveGameToState(LeaveGame event) async* { + try { + yield state.copyWith(leavingGame: event.game); + await _gameRepository.leaveGame(event.game.id); + yield state.copyWith(leavingGame: null); + } catch (e) { + yield state.copyWith(leavingGame: null, error: "$e"); + } + } + + Stream _mapJoinGameToState(JoinGame event) async* { + try { + yield state.copyWith(joiningGame: event.gameCode); + var game = await _gameRepository.joinGame(event.gameCode); + yield state.copyWith(joiningGame: null, joinedGame: game, overrideNull: true); + } catch (e) { + yield state.copyWith(joiningGame: null, joinedGame: null, error: "$e", overrideNull: true); + } + } } diff --git a/lib/ui/home/bloc/home_event.dart b/lib/ui/home/bloc/home_event.dart index 7c535b4..b49bbeb 100644 --- a/lib/ui/home/bloc/home_event.dart +++ b/lib/ui/home/bloc/home_event.dart @@ -31,3 +31,23 @@ class UserUpdated extends HomeEvent { @override List get props => [user]; } + +@immutable +class LeaveGame extends HomeEvent { + final UserGame game; + + LeaveGame(this.game); + + @override + List get props => [game]; +} + +@immutable +class JoinGame extends HomeEvent { + final String gameCode; + + JoinGame(this.gameCode); + + @override + List get props => [gameCode]; +} diff --git a/lib/ui/home/bloc/home_state.dart b/lib/ui/home/bloc/home_state.dart index 901513d..43a2eb1 100644 --- a/lib/ui/home/bloc/home_state.dart +++ b/lib/ui/home/bloc/home_state.dart @@ -1,3 +1,4 @@ +import 'package:appsagainsthumanity/data/features/game/model/game.dart'; import 'package:appsagainsthumanity/data/features/users/model/user.dart'; import 'package:appsagainsthumanity/data/features/users/model/user_game.dart'; import 'package:meta/meta.dart'; @@ -6,6 +7,9 @@ import 'package:meta/meta.dart'; class HomeState { final User user; final List games; + final UserGame leavingGame; + final String joiningGame; + final Game joinedGame; final String error; final bool isLoading; @@ -13,6 +17,9 @@ class HomeState { @required this.user, @required this.isLoading, List games, + this.leavingGame, + this.joiningGame, + this.joinedGame, this.error, }) : games = games ?? []; @@ -27,14 +34,21 @@ class HomeState { HomeState copyWith({ User user, List games, + UserGame leavingGame, + String joiningGame, + Game joinedGame, bool isLoading, String error, + bool overrideNull = false }) { return HomeState( user: user ?? this.user, games: games ?? this.games, + leavingGame: overrideNull ? leavingGame : leavingGame ?? this.leavingGame, + joiningGame: overrideNull ? joiningGame : joiningGame ?? this.joiningGame, + joinedGame: overrideNull ? joinedGame : joinedGame ?? this.joinedGame, isLoading: isLoading ?? this.isLoading, - error: error ?? this.error, + error: overrideNull ? error : error ?? this.error, ); } @@ -44,6 +58,9 @@ class HomeState { user: $user, games: $games, isLoading: $isLoading, + leavingGame: $leavingGame, + joiningGame: $joiningGame, + joinedGame: $joinedGame, error: $error }'''; } diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index 004bef2..3f2016d 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -37,27 +37,45 @@ class _HomeScreenState extends State { Widget build(BuildContext context) { return BlocProvider( create: (context) => HomeBloc(context.repository(), context.repository())..add(HomeStarted()), - child: _buildBody(), + child: MultiBlocListener( + listeners: [ + // Error Listener + BlocListener( + condition: (previous, current) => previous.error != current.error && current.error != null, + listener: (context, state) { + if (state.error != null) { + Scaffold.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text(state.error), Icon(Icons.error)], + ), + backgroundColor: Colors.redAccent, + ), + ); + } + }, + ), + + // Joined Game listener that opens a 'joined' game + BlocListener( + condition: (previous, current) => previous.joinedGame?.id != current.joinedGame?.id, + listener: (context, state) { + if (state.joinedGame != null) { + Navigator.of(context).push(GamePageRoute(state.joinedGame)); + } + }, + ) + ], + child: _buildBody(), + ), ); } Widget _buildBody() { - return BlocConsumer( - listener: (BuildContext context, HomeState state) { - if (state.error != null) { - Scaffold.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [Text(state.error), Icon(Icons.error)], - ), - backgroundColor: Colors.redAccent, - ), - ); - } - }, + return BlocBuilder( builder: (context, state) { return Scaffold( body: Padding( @@ -70,9 +88,7 @@ class _HomeScreenState extends State { controller: _pageController, children: [ _buildTitleCard(context, state, includeMargin: false), - - if (state.games.isNotEmpty) - PastGamesCard(state), + if (state.games.isNotEmpty) PastGamesCard(state), ], ), ), @@ -80,28 +96,34 @@ class _HomeScreenState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - _buildMenuAction( - context: context, - margin: const EdgeInsets.only(left: 24, top: 16, right: 8, bottom: 16), - icon: MdiIcons.gamepad, - label: "START GAME", - onTap: () { - PushNotifications().checkPermissions(); - Navigator.of(context).push(MaterialPageRoute(builder: (context) => CreateGameScreen())); - }, - ), - Builder(builder: (context) { - return _buildMenuAction( + if (state.joiningGame == null) + _buildMenuAction( context: context, - margin: const EdgeInsets.only(left: 8, top: 16, right: 24, bottom: 16), - icon: MdiIcons.gamepadVariantOutline, - label: "JOIN GAME", + margin: const EdgeInsets.only(left: 24, top: 16, right: 8, bottom: 16), + icon: MdiIcons.gamepad, + label: "START GAME", onTap: () { PushNotifications().checkPermissions(); - _joinGame(context); + Navigator.of(context).push(MaterialPageRoute(builder: (context) => CreateGameScreen())); }, - ); - }), + ), + _buildMenuAction( + context: context, + margin: EdgeInsets.only( + left: state.joiningGame == null ? 8 : 24, + top: 16, + right: 24, + bottom: 16, + ), + icon: MdiIcons.gamepadVariantOutline, + label: state.joiningGame != null ? "JOINING GAME..." : "JOIN GAME", + onTap: state.joiningGame == null + ? () { + PushNotifications().checkPermissions(); + _joinGame(context); + } + : null, + ), ], ), ) @@ -160,7 +182,9 @@ class _HomeScreenState extends State { child: Container( child: state.isLoading ? _buildLoadingUserTile() - : state.error != null ? _buildErrorUserTile(state.error) : _buildUserTile(context, state.user)), + : state.error != null + ? _buildErrorUserTile(state.error) + : _buildUserTile(context, state.user)), ), ) ], @@ -179,7 +203,9 @@ class _HomeScreenState extends State { VoidCallback onTap, }) { return Expanded( - child: Container( + child: AnimatedContainer( + duration: Duration(milliseconds: 150), + curve: Curves.fastOutSlowIn, margin: margin, child: Material( borderRadius: BorderRadius.circular(16), @@ -248,9 +274,7 @@ class _HomeScreenState extends State { Widget _buildUserTile(BuildContext context, User user) { return ListTile( onTap: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (_) => ProfileScreen() - )); + Navigator.of(context).push(MaterialPageRoute(builder: (_) => ProfileScreen())); }, contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), title: Text( @@ -272,16 +296,7 @@ class _HomeScreenState extends State { void _joinGame(BuildContext context) async { var gameId = await showJoinRoomDialog(context); if (gameId != null) { - try { - var game = await context.repository().joinGame(gameId); - Navigator.of(context).push(GamePageRoute(game)); - } catch (e) { - Scaffold.of(context) - ..hideCurrentSnackBar() - ..showSnackBar(SnackBar( - content: Text("$e"), - )); - } + context.bloc().add(JoinGame(gameId)); } } } diff --git a/lib/ui/home/widgets/past_game_card.dart b/lib/ui/home/widgets/past_game_card.dart index 5458a56..0749901 100644 --- a/lib/ui/home/widgets/past_game_card.dart +++ b/lib/ui/home/widgets/past_game_card.dart @@ -47,7 +47,8 @@ class PastGamesCard extends StatelessWidget { itemCount: state.games.length, itemBuilder: (context, index) { var game = state.games[index]; - return PastGame(game); + var isLeavingGame = game.id == state.leavingGame?.id; + return PastGame(game, isLeavingGame); }), ) ], @@ -74,8 +75,9 @@ class PastGamesCard extends StatelessWidget { class PastGame extends StatelessWidget { final UserGame game; + final bool isLeavingGame; - PastGame(this.game); + PastGame(this.game, this.isLeavingGame); @override Widget build(BuildContext context) { @@ -85,13 +87,15 @@ class PastGame extends StatelessWidget { style: context.theme.textTheme.subtitle1.copyWith(color: AppColors.primary, fontWeight: FontWeight.w500), ), subtitle: Text( - game.state.label, + isLeavingGame ? "Leaving..." : game.state.label, style: context.theme.textTheme.bodyText2.copyWith(color: Colors.black54), ), - trailing: Text( - timeago.format(game.joinedAt), - style: context.theme.textTheme.bodyText2.copyWith(color: Colors.black26), - ), + trailing: isLeavingGame + ? Container(width: 24, height: 24,child: CircularProgressIndicator()) + : Text( + timeago.format(game.joinedAt), + style: context.theme.textTheme.bodyText2.copyWith(color: Colors.black26), + ), onTap: game.state == GameState.inProgress || game.state == GameState.waitingRoom ? () => openGame(context) : null, onLongPress: () => leaveGame(context), ); @@ -134,7 +138,7 @@ class PastGame extends StatelessWidget { }); if (result == true) { - await context.repository().leaveGame(game.id); + context.bloc().add(LeaveGame(game)); } } } diff --git a/lib/ui/profile/profile_screen.dart b/lib/ui/profile/profile_screen.dart index 7c0a78f..2267afa 100644 --- a/lib/ui/profile/profile_screen.dart +++ b/lib/ui/profile/profile_screen.dart @@ -39,16 +39,16 @@ class _ProfileScreenState extends State { title: Text("Profile"), backgroundColor: AppColors.surfaceDark, ), - body: Column( + body: ListView( children: [ Container( - padding: const EdgeInsets.only(top: 56), + padding: const EdgeInsets.only(top: 32), child: Stack( alignment: Alignment.center, children: [ ProfilePhoto( state.user, - size: 196, + size: 128, ), if (state.isLoading) CircularProgressIndicator(), if (!state.isLoading && state.user?.avatarUrl != null) _buildEditPhotoStack(context) @@ -93,14 +93,14 @@ class _ProfileScreenState extends State { return IgnorePointer( ignoring: true, child: Container( - margin: const EdgeInsets.only(top: 72), + margin: const EdgeInsets.only(top: 8), child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( MdiIcons.imageEditOutline, - color: Colors.white, + color: AppColors.primaryVariant, ), Container( margin: const EdgeInsets.only(left: 8), @@ -108,7 +108,7 @@ class _ProfileScreenState extends State { "EDIT", style: context.theme.textTheme.subtitle1.copyWith( fontWeight: FontWeight.w700, - color: Colors.white, + color: AppColors.primaryVariant, ), ), ) From 54a4be5e3f73e0dc150b11eca3edb4344468d5f6 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Thu, 30 Apr 2020 10:01:22 -0400 Subject: [PATCH 24/67] Added loading indication to create game screen --- lib/ui/creategame/bloc/create_game_bloc.dart | 19 ++ lib/ui/creategame/bloc/create_game_event.dart | 2 + lib/ui/creategame/bloc/create_game_state.dart | 13 +- lib/ui/creategame/create_game_screen.dart | 163 ++++++++++-------- 4 files changed, 118 insertions(+), 79 deletions(-) diff --git a/lib/ui/creategame/bloc/create_game_bloc.dart b/lib/ui/creategame/bloc/create_game_bloc.dart index 627fb16..213441d 100644 --- a/lib/ui/creategame/bloc/create_game_bloc.dart +++ b/lib/ui/creategame/bloc/create_game_bloc.dart @@ -31,6 +31,8 @@ class CreateGameBloc extends Bloc { yield* _mapChangePick2EnabledToState(event); } else if (event is ChangeDraw2Pick3Enabled) { yield* _mapChangeDraw2Pick3EnabledToState(event); + } else if (event is CreateGame) { + yield* _mapCreateGameToState(); } } @@ -84,4 +86,21 @@ class CreateGameBloc extends Bloc { Stream _mapChangeDraw2Pick3EnabledToState(ChangeDraw2Pick3Enabled event) async* { yield state.copyWith(draw2pick3Enabled: event.enabled); } + + Stream _mapCreateGameToState() async* { + yield state.copyWith(isLoading: true, error: null); + try { + var game = await gameRepository.createGame( + state.selectedSets, + prizesToWin: state.prizesToWin, + playerLimit: state.playerLimit, + pick2Enabled: state.pick2Enabled, + draw2Pick3Enabled: state.draw2pick3Enabled, + ); + yield state.copyWith(createdGame: game, isLoading: false); + } catch (e, st) { + print("Create Game Error: $e\n$st"); + yield state.copyWith(isLoading: false, error: "$e"); + } + } } diff --git a/lib/ui/creategame/bloc/create_game_event.dart b/lib/ui/creategame/bloc/create_game_event.dart index 678aa3c..5fdaf92 100644 --- a/lib/ui/creategame/bloc/create_game_event.dart +++ b/lib/ui/creategame/bloc/create_game_event.dart @@ -64,3 +64,5 @@ class ChangeDraw2Pick3Enabled extends CreateGameEvent { @override List get props => [enabled]; } + +class CreateGame extends CreateGameEvent {} diff --git a/lib/ui/creategame/bloc/create_game_state.dart b/lib/ui/creategame/bloc/create_game_state.dart index 6a050bf..c4b3359 100644 --- a/lib/ui/creategame/bloc/create_game_state.dart +++ b/lib/ui/creategame/bloc/create_game_state.dart @@ -1,5 +1,6 @@ import 'package:appsagainsthumanity/data/app_preferences.dart'; import 'package:appsagainsthumanity/data/features/cards/model/card_set.dart'; +import 'package:appsagainsthumanity/data/features/game/model/game.dart'; import 'package:kt_dart/kt.dart'; import 'package:meta/meta.dart'; @@ -12,6 +13,7 @@ class CreateGameState { final bool pick2Enabled; final bool draw2pick3Enabled; + final Game createdGame; final bool isLoading; final String error; @@ -23,7 +25,8 @@ class CreateGameState { this.prizesToWin = 7, this.playerLimit = 15, this.pick2Enabled = true, - this.draw2pick3Enabled = true + this.draw2pick3Enabled = true, + this.createdGame, }); factory CreateGameState.empty() { @@ -45,6 +48,8 @@ class CreateGameState { bool draw2pick3Enabled, bool isLoading, String error, + Game createdGame, + bool overrideNull = false }) { return CreateGameState( cardSets: cardSets ?? this.cardSets, @@ -54,7 +59,8 @@ class CreateGameState { pick2Enabled: pick2Enabled ?? this.pick2Enabled, draw2pick3Enabled: draw2pick3Enabled ?? this.draw2pick3Enabled, isLoading: isLoading ?? this.isLoading, - error: error ?? this.error, + error: overrideNull ? error : error ?? this.error, + createdGame: overrideNull ? createdGame : createdGame ?? this.createdGame ); } @@ -68,7 +74,8 @@ class CreateGameState { pick2Enabled: $pick2Enabled, draw2pick3Enabled: $draw2pick3Enabled, isLoading: $isLoading, - error: $error + error: $error, + createdGame: $createdGame, }'''; } } diff --git a/lib/ui/creategame/create_game_screen.dart b/lib/ui/creategame/create_game_screen.dart index ecc7091..9968ae5 100644 --- a/lib/ui/creategame/create_game_screen.dart +++ b/lib/ui/creategame/create_game_screen.dart @@ -21,93 +21,104 @@ class _CreateGameScreenState extends State { context.repository(), context.repository(), )..add(ScreenLoaded()), - child: _buildScaffold(), + child: MultiBlocListener( + listeners: [ + BlocListener( + condition: (previous, current) => current.error != previous.error, + listener: (context, state) { + if (state.error != null) { + Scaffold.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text(state.error), Icon(Icons.error)], + ), + backgroundColor: Colors.redAccent, + ), + ); + } + }, + ), + BlocListener( + condition: (previous, current) => current.createdGame?.id != previous.createdGame?.id, + listener: (context, state) { + if (state.createdGame != null) { + Navigator.of(context).pushReplacement(GamePageRoute(state.createdGame)); + } + }, + ), + ], + child: _buildScaffold(), + ), ); } Widget _buildScaffold() { - return BlocConsumer(listener: (context, state) { - if (state.error != null) { - Scaffold.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [Text(state.error), Icon(Icons.error)], + return BlocBuilder( + builder: (context, state) { + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: AppBar( + title: Text(context.strings.titleNewGame), + backgroundColor: AppColors.surfaceDark, + leading: IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + bottom: TabBar( + tabs: [ + Tab(text: "CARDS"), + Tab(text: "OPTIONS"), + ], ), - backgroundColor: Colors.redAccent, - ), - ); - } - }, builder: (context, state) { - return DefaultTabController( - length: 2, - child: Scaffold( - appBar: AppBar( - title: Text(context.strings.titleNewGame), - backgroundColor: AppColors.surfaceDark, - leading: IconButton( - icon: Icon(Icons.arrow_back), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - bottom: TabBar( - tabs: [ - Tab(text: "CARDS"), - Tab(text: "OPTIONS"), - ], ), - ), - bottomNavigationBar: BottomAppBar( - notchMargin: 8, - color: AppColors.surfaceDark, - shape: CircularNotchedRectangle(), - child: Container( - height: 56, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - "${state.selectedSets.size} Selected", - style: context.theme.textTheme.headline6, + bottomNavigationBar: BottomAppBar( + notchMargin: 8, + color: AppColors.surfaceDark, + shape: CircularNotchedRectangle(), + child: Container( + height: 56, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + state.isLoading ? "Loading..." : "${state.selectedSets.size} Selected", + style: context.theme.textTheme.headline6, + ), ), ), - ), - ], + ], + ), ), ), + floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, + floatingActionButton: state.selectedSets.isNotEmpty() && !state.isLoading + ? FloatingActionButton( + child: Icon(Icons.check), + onPressed: () async { + // Start game? + context.bloc().add(CreateGame()); + }, + ) + : null, + body: TabBarView( + children: [ + _buildCardSetLists(state), + _buildGameOptions(context, state), + ], + ), ), - floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, - floatingActionButton: state.selectedSets.isNotEmpty() - ? FloatingActionButton( - child: Icon(Icons.check), - onPressed: () async { - // Start game? - var newGame = await context.repository().createGame( - state.selectedSets, - prizesToWin: state.prizesToWin, - playerLimit: state.playerLimit, - pick2Enabled: state.pick2Enabled, - draw2Pick3Enabled: state.draw2pick3Enabled, - ); - Navigator.of(context).pushReplacement(GamePageRoute(newGame)); - }, - ) - : null, - body: TabBarView( - children: [ - _buildCardSetLists(state), - _buildGameOptions(context, state), - ], - ), - ), - ); - }); + ); + }, + ); } Widget _buildCardSetLists(CreateGameState state) { From 8b0fd6e88214a60203b5bbc2d7107763ced8adc9 Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Thu, 30 Apr 2020 14:14:15 -0400 Subject: [PATCH 25/67] Added some iOS configuration so that dynamic links can link into the application easily --- android/app/src/main/AndroidManifest.xml | 8 ++++++++ ios/Runner/Info.plist | 4 ++++ ios/Runner/Runner.entitlements | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index bd8afec..4721ca5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -43,6 +43,14 @@ + + + + + + - + + - + - - + + - + + - + - - + + + + + - + + From 09f2d6f47b173a3fd872aa83b1ec8c737369f26b Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Fri, 14 May 2021 11:49:37 -0400 Subject: [PATCH 65/67] adding locks --- ios/Podfile.lock | 574 +++++++++++++++++++++++ pubspec.lock | 1130 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1704 insertions(+) create mode 100644 ios/Podfile.lock create mode 100644 pubspec.lock diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..07cc1a0 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,574 @@ +PODS: + - abseil/algorithm (0.20200225.0): + - abseil/algorithm/algorithm (= 0.20200225.0) + - abseil/algorithm/container (= 0.20200225.0) + - abseil/algorithm/algorithm (0.20200225.0): + - abseil/base/config + - abseil/algorithm/container (0.20200225.0): + - abseil/algorithm/algorithm + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/base (0.20200225.0): + - abseil/base/atomic_hook (= 0.20200225.0) + - abseil/base/base (= 0.20200225.0) + - abseil/base/base_internal (= 0.20200225.0) + - abseil/base/bits (= 0.20200225.0) + - abseil/base/config (= 0.20200225.0) + - abseil/base/core_headers (= 0.20200225.0) + - abseil/base/dynamic_annotations (= 0.20200225.0) + - abseil/base/endian (= 0.20200225.0) + - abseil/base/errno_saver (= 0.20200225.0) + - abseil/base/exponential_biased (= 0.20200225.0) + - abseil/base/log_severity (= 0.20200225.0) + - abseil/base/malloc_internal (= 0.20200225.0) + - abseil/base/periodic_sampler (= 0.20200225.0) + - abseil/base/pretty_function (= 0.20200225.0) + - abseil/base/raw_logging_internal (= 0.20200225.0) + - abseil/base/spinlock_wait (= 0.20200225.0) + - abseil/base/throw_delegate (= 0.20200225.0) + - abseil/base/atomic_hook (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/base (0.20200225.0): + - abseil/base/atomic_hook + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/log_severity + - abseil/base/raw_logging_internal + - abseil/base/spinlock_wait + - abseil/meta/type_traits + - abseil/base/base_internal (0.20200225.0): + - abseil/base/config + - abseil/meta/type_traits + - abseil/base/bits (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/config (0.20200225.0) + - abseil/base/core_headers (0.20200225.0): + - abseil/base/config + - abseil/base/dynamic_annotations (0.20200225.0) + - abseil/base/endian (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/errno_saver (0.20200225.0): + - abseil/base/config + - abseil/base/exponential_biased (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/log_severity (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/malloc_internal (0.20200225.0): + - abseil/base/base + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/raw_logging_internal + - abseil/base/periodic_sampler (0.20200225.0): + - abseil/base/core_headers + - abseil/base/exponential_biased + - abseil/base/pretty_function (0.20200225.0) + - abseil/base/raw_logging_internal (0.20200225.0): + - abseil/base/atomic_hook + - abseil/base/config + - abseil/base/core_headers + - abseil/base/log_severity + - abseil/base/spinlock_wait (0.20200225.0): + - abseil/base/base_internal + - abseil/base/core_headers + - abseil/base/errno_saver + - abseil/base/throw_delegate (0.20200225.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/container/compressed_tuple (0.20200225.0): + - abseil/utility/utility + - abseil/container/inlined_vector (0.20200225.0): + - abseil/algorithm/algorithm + - abseil/base/core_headers + - abseil/base/throw_delegate + - abseil/container/inlined_vector_internal + - abseil/memory/memory + - abseil/container/inlined_vector_internal (0.20200225.0): + - abseil/base/core_headers + - abseil/container/compressed_tuple + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/types/span + - abseil/memory (0.20200225.0): + - abseil/memory/memory (= 0.20200225.0) + - abseil/memory/memory (0.20200225.0): + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/meta (0.20200225.0): + - abseil/meta/type_traits (= 0.20200225.0) + - abseil/meta/type_traits (0.20200225.0): + - abseil/base/config + - abseil/numeric/int128 (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/strings/internal (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/raw_logging_internal + - abseil/meta/type_traits + - abseil/strings/str_format (0.20200225.0): + - abseil/strings/str_format_internal + - abseil/strings/str_format_internal (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/numeric/int128 + - abseil/strings/strings + - abseil/types/span + - abseil/strings/strings (0.20200225.0): + - abseil/base/base + - abseil/base/bits + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/raw_logging_internal + - abseil/base/throw_delegate + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/numeric/int128 + - abseil/strings/internal + - abseil/time (0.20200225.0): + - abseil/time/internal (= 0.20200225.0) + - abseil/time/time (= 0.20200225.0) + - abseil/time/internal (0.20200225.0): + - abseil/time/internal/cctz (= 0.20200225.0) + - abseil/time/internal/cctz (0.20200225.0): + - abseil/time/internal/cctz/civil_time (= 0.20200225.0) + - abseil/time/internal/cctz/time_zone (= 0.20200225.0) + - abseil/time/internal/cctz/civil_time (0.20200225.0): + - abseil/base/config + - abseil/time/internal/cctz/time_zone (0.20200225.0): + - abseil/base/config + - abseil/time/internal/cctz/civil_time + - abseil/time/time (0.20200225.0): + - abseil/base/base + - abseil/base/core_headers + - abseil/base/raw_logging_internal + - abseil/numeric/int128 + - abseil/strings/strings + - abseil/time/internal/cctz/civil_time + - abseil/time/internal/cctz/time_zone + - abseil/types (0.20200225.0): + - abseil/types/any (= 0.20200225.0) + - abseil/types/bad_any_cast (= 0.20200225.0) + - abseil/types/bad_any_cast_impl (= 0.20200225.0) + - abseil/types/bad_optional_access (= 0.20200225.0) + - abseil/types/bad_variant_access (= 0.20200225.0) + - abseil/types/compare (= 0.20200225.0) + - abseil/types/optional (= 0.20200225.0) + - abseil/types/span (= 0.20200225.0) + - abseil/types/variant (= 0.20200225.0) + - abseil/types/any (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/types/bad_any_cast + - abseil/utility/utility + - abseil/types/bad_any_cast (0.20200225.0): + - abseil/base/config + - abseil/types/bad_any_cast_impl + - abseil/types/bad_any_cast_impl (0.20200225.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/types/bad_optional_access (0.20200225.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/types/bad_variant_access (0.20200225.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/types/compare (0.20200225.0): + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/types/optional (0.20200225.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/types/bad_optional_access + - abseil/utility/utility + - abseil/types/span (0.20200225.0): + - abseil/algorithm/algorithm + - abseil/base/core_headers + - abseil/base/throw_delegate + - abseil/meta/type_traits + - abseil/types/variant (0.20200225.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/types/bad_variant_access + - abseil/utility/utility + - abseil/utility/utility (0.20200225.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/meta/type_traits + - AppAuth (1.4.0): + - AppAuth/Core (= 1.4.0) + - AppAuth/ExternalUserAgent (= 1.4.0) + - AppAuth/Core (1.4.0) + - AppAuth/ExternalUserAgent (1.4.0) + - BoringSSL-GRPC (0.0.7): + - BoringSSL-GRPC/Implementation (= 0.0.7) + - BoringSSL-GRPC/Interface (= 0.0.7) + - BoringSSL-GRPC/Implementation (0.0.7): + - BoringSSL-GRPC/Interface (= 0.0.7) + - BoringSSL-GRPC/Interface (0.0.7) + - cloud_firestore (1.0.1): + - Firebase/Firestore (= 7.3.0) + - firebase_core + - Flutter + - cloud_functions (1.0.0): + - Firebase/Functions (= 7.3.0) + - firebase_core + - Flutter + - device_info (0.0.1): + - Flutter + - Firebase/Analytics (7.3.0): + - Firebase/Core + - Firebase/Auth (7.3.0): + - Firebase/CoreOnly + - FirebaseAuth (~> 7.3.0) + - Firebase/Core (7.3.0): + - Firebase/CoreOnly + - FirebaseAnalytics (= 7.3.0) + - Firebase/CoreOnly (7.3.0): + - FirebaseCore (= 7.3.0) + - Firebase/DynamicLinks (7.3.0): + - Firebase/CoreOnly + - FirebaseDynamicLinks (~> 7.3.0) + - Firebase/Firestore (7.3.0): + - Firebase/CoreOnly + - FirebaseFirestore (~> 7.3.0) + - Firebase/Functions (7.3.0): + - Firebase/CoreOnly + - FirebaseFunctions (~> 7.3.0) + - Firebase/Messaging (7.3.0): + - Firebase/CoreOnly + - FirebaseMessaging (~> 7.3.0) + - Firebase/Storage (7.3.0): + - Firebase/CoreOnly + - FirebaseStorage (~> 7.3.0) + - firebase_analytics (7.1.1): + - Firebase/Analytics (= 7.3.0) + - firebase_core + - Flutter + - firebase_auth (1.0.1): + - Firebase/Auth (= 7.3.0) + - firebase_core + - Flutter + - firebase_core (1.0.1): + - Firebase/CoreOnly (= 7.3.0) + - Flutter + - firebase_dynamic_links (0.8.0): + - Firebase/DynamicLinks (= 7.3.0) + - firebase_core + - Flutter + - firebase_messaging (9.0.0): + - Firebase/Messaging (= 7.3.0) + - firebase_core + - Flutter + - firebase_storage (8.0.0): + - Firebase/Storage (= 7.3.0) + - firebase_core + - Flutter + - FirebaseAnalytics (7.3.0): + - FirebaseCore (~> 7.0) + - FirebaseInstallations (~> 7.0) + - GoogleAppMeasurement (= 7.3.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.0) + - GoogleUtilities/MethodSwizzler (~> 7.0) + - GoogleUtilities/Network (~> 7.0) + - "GoogleUtilities/NSData+zlib (~> 7.0)" + - nanopb (~> 2.30906.0) + - FirebaseAuth (7.3.0): + - FirebaseCore (~> 7.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.0) + - GoogleUtilities/Environment (~> 7.0) + - GTMSessionFetcher/Core (~> 1.4) + - FirebaseCore (7.3.0): + - FirebaseCoreDiagnostics (~> 7.0) + - GoogleUtilities/Environment (~> 7.0) + - GoogleUtilities/Logger (~> 7.0) + - FirebaseCoreDiagnostics (7.3.0): + - GoogleDataTransport (~> 8.0) + - GoogleUtilities/Environment (~> 7.0) + - GoogleUtilities/Logger (~> 7.0) + - nanopb (~> 2.30906.0) + - FirebaseDynamicLinks (7.3.1): + - FirebaseCore (~> 7.0) + - FirebaseFirestore (7.3.0): + - abseil/algorithm (= 0.20200225.0) + - abseil/base (= 0.20200225.0) + - abseil/memory (= 0.20200225.0) + - abseil/meta (= 0.20200225.0) + - abseil/strings/strings (= 0.20200225.0) + - abseil/time (= 0.20200225.0) + - abseil/types (= 0.20200225.0) + - FirebaseCore (~> 7.0) + - "gRPC-C++ (~> 1.28.0)" + - leveldb-library (~> 1.22) + - nanopb (~> 2.30906.0) + - FirebaseFunctions (7.3.0): + - FirebaseCore (~> 7.0) + - GTMSessionFetcher/Core (~> 1.4) + - FirebaseInstallations (7.8.0): + - FirebaseCore (~> 7.0) + - GoogleUtilities/Environment (~> 7.0) + - GoogleUtilities/UserDefaults (~> 7.0) + - PromisesObjC (~> 1.2) + - FirebaseInstanceID (7.8.0): + - FirebaseCore (~> 7.0) + - FirebaseInstallations (~> 7.0) + - GoogleUtilities/Environment (~> 7.0) + - GoogleUtilities/UserDefaults (~> 7.0) + - FirebaseMessaging (7.3.0): + - FirebaseCore (~> 7.0) + - FirebaseInstanceID (~> 7.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.0) + - GoogleUtilities/Environment (~> 7.0) + - GoogleUtilities/Reachability (~> 7.0) + - GoogleUtilities/UserDefaults (~> 7.0) + - FirebaseStorage (7.3.0): + - FirebaseCore (~> 7.0) + - GTMSessionFetcher/Core (~> 1.4) + - Flutter (1.0.0) + - google_sign_in (0.0.1): + - Flutter + - GoogleSignIn (~> 5.0) + - GoogleAppMeasurement (7.3.0): + - GoogleUtilities/AppDelegateSwizzler (~> 7.0) + - GoogleUtilities/MethodSwizzler (~> 7.0) + - GoogleUtilities/Network (~> 7.0) + - "GoogleUtilities/NSData+zlib (~> 7.0)" + - nanopb (~> 2.30906.0) + - GoogleDataTransport (8.1.0): + - nanopb (~> 2.30906.0) + - GoogleSignIn (5.0.2): + - AppAuth (~> 1.2) + - GTMAppAuth (~> 1.0) + - GTMSessionFetcher/Core (~> 1.1) + - GoogleUtilities/AppDelegateSwizzler (7.3.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (7.3.0): + - PromisesObjC (~> 1.2) + - GoogleUtilities/Logger (7.3.0): + - GoogleUtilities/Environment + - GoogleUtilities/MethodSwizzler (7.3.0): + - GoogleUtilities/Logger + - GoogleUtilities/Network (7.3.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (7.3.0)" + - GoogleUtilities/Reachability (7.3.0): + - GoogleUtilities/Logger + - GoogleUtilities/UserDefaults (7.3.0): + - GoogleUtilities/Logger + - "gRPC-C++ (1.28.2)": + - "gRPC-C++/Implementation (= 1.28.2)" + - "gRPC-C++/Interface (= 1.28.2)" + - "gRPC-C++/Implementation (1.28.2)": + - abseil/container/inlined_vector (= 0.20200225.0) + - abseil/memory/memory (= 0.20200225.0) + - abseil/strings/str_format (= 0.20200225.0) + - abseil/strings/strings (= 0.20200225.0) + - abseil/types/optional (= 0.20200225.0) + - "gRPC-C++/Interface (= 1.28.2)" + - gRPC-Core (= 1.28.2) + - "gRPC-C++/Interface (1.28.2)" + - gRPC-Core (1.28.2): + - gRPC-Core/Implementation (= 1.28.2) + - gRPC-Core/Interface (= 1.28.2) + - gRPC-Core/Implementation (1.28.2): + - abseil/container/inlined_vector (= 0.20200225.0) + - abseil/memory/memory (= 0.20200225.0) + - abseil/strings/str_format (= 0.20200225.0) + - abseil/strings/strings (= 0.20200225.0) + - abseil/types/optional (= 0.20200225.0) + - BoringSSL-GRPC (= 0.0.7) + - gRPC-Core/Interface (= 1.28.2) + - gRPC-Core/Interface (1.28.2) + - GTMAppAuth (1.1.0): + - AppAuth/Core (~> 1.4) + - GTMSessionFetcher (~> 1.4) + - GTMSessionFetcher (1.5.0): + - GTMSessionFetcher/Full (= 1.5.0) + - GTMSessionFetcher/Core (1.5.0) + - GTMSessionFetcher/Full (1.5.0): + - GTMSessionFetcher/Core (= 1.5.0) + - image_picker (0.0.1): + - Flutter + - leveldb-library (1.22.1) + - nanopb (2.30906.0): + - nanopb/decode (= 2.30906.0) + - nanopb/encode (= 2.30906.0) + - nanopb/decode (2.30906.0) + - nanopb/encode (2.30906.0) + - package_info (0.0.1): + - Flutter + - path_provider (0.0.1): + - Flutter + - PromisesObjC (1.2.12) + - share (0.0.1): + - Flutter + - shared_preferences (0.0.1): + - Flutter + - sign_in_with_apple (0.0.1): + - Flutter + - url_launcher (0.0.1): + - Flutter + - webview_flutter (0.0.1): + - Flutter + +DEPENDENCIES: + - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) + - cloud_functions (from `.symlinks/plugins/cloud_functions/ios`) + - device_info (from `.symlinks/plugins/device_info/ios`) + - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) + - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - firebase_dynamic_links (from `.symlinks/plugins/firebase_dynamic_links/ios`) + - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) + - firebase_storage (from `.symlinks/plugins/firebase_storage/ios`) + - Flutter (from `Flutter`) + - google_sign_in (from `.symlinks/plugins/google_sign_in/ios`) + - image_picker (from `.symlinks/plugins/image_picker/ios`) + - package_info (from `.symlinks/plugins/package_info/ios`) + - path_provider (from `.symlinks/plugins/path_provider/ios`) + - share (from `.symlinks/plugins/share/ios`) + - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) + - sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`) + - url_launcher (from `.symlinks/plugins/url_launcher/ios`) + - webview_flutter (from `.symlinks/plugins/webview_flutter/ios`) + +SPEC REPOS: + trunk: + - abseil + - AppAuth + - BoringSSL-GRPC + - Firebase + - FirebaseAnalytics + - FirebaseAuth + - FirebaseCore + - FirebaseCoreDiagnostics + - FirebaseDynamicLinks + - FirebaseFirestore + - FirebaseFunctions + - FirebaseInstallations + - FirebaseInstanceID + - FirebaseMessaging + - FirebaseStorage + - GoogleAppMeasurement + - GoogleDataTransport + - GoogleSignIn + - GoogleUtilities + - "gRPC-C++" + - gRPC-Core + - GTMAppAuth + - GTMSessionFetcher + - leveldb-library + - nanopb + - PromisesObjC + +EXTERNAL SOURCES: + cloud_firestore: + :path: ".symlinks/plugins/cloud_firestore/ios" + cloud_functions: + :path: ".symlinks/plugins/cloud_functions/ios" + device_info: + :path: ".symlinks/plugins/device_info/ios" + firebase_analytics: + :path: ".symlinks/plugins/firebase_analytics/ios" + firebase_auth: + :path: ".symlinks/plugins/firebase_auth/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + firebase_dynamic_links: + :path: ".symlinks/plugins/firebase_dynamic_links/ios" + firebase_messaging: + :path: ".symlinks/plugins/firebase_messaging/ios" + firebase_storage: + :path: ".symlinks/plugins/firebase_storage/ios" + Flutter: + :path: Flutter + google_sign_in: + :path: ".symlinks/plugins/google_sign_in/ios" + image_picker: + :path: ".symlinks/plugins/image_picker/ios" + package_info: + :path: ".symlinks/plugins/package_info/ios" + path_provider: + :path: ".symlinks/plugins/path_provider/ios" + share: + :path: ".symlinks/plugins/share/ios" + shared_preferences: + :path: ".symlinks/plugins/shared_preferences/ios" + sign_in_with_apple: + :path: ".symlinks/plugins/sign_in_with_apple/ios" + url_launcher: + :path: ".symlinks/plugins/url_launcher/ios" + webview_flutter: + :path: ".symlinks/plugins/webview_flutter/ios" + +SPEC CHECKSUMS: + abseil: 6c8eb7892aefa08d929b39f9bb108e5367e3228f + AppAuth: 31bcec809a638d7bd2f86ea8a52bd45f6e81e7c7 + BoringSSL-GRPC: 8edf627ee524575e2f8d19d56f068b448eea3879 + cloud_firestore: 69d71054fcab9a1c0d4779362f85b94aec7ff7d7 + cloud_functions: 32ea5ece6048a6029d3280df62afe8ff85598ce9 + device_info: d7d233b645a32c40dfdc212de5cf646ca482f175 + Firebase: 26223c695fe322633274198cb19dca8cb7e54416 + firebase_analytics: ee4abb07bfa857e7c95d97144efe9eb68be414cd + firebase_auth: 9f6491ea8e44570323361ae713a2ae3175b3f21a + firebase_core: d2e03528e2a600891f6f460b5e92932624480d1d + firebase_dynamic_links: e222260696317885f2f2592fddf8783158f4e7df + firebase_messaging: fc1811236795c2313b8339c35d31295b1cd8486f + firebase_storage: a4b292db6551a64c3697c7e67cb1df524b494bb1 + FirebaseAnalytics: 2580c2d62535ae7b644143d48941fcc239ea897a + FirebaseAuth: c224a0cf1afa0949bd5c7bfcf154b4f5ce8ddef2 + FirebaseCore: 4d3c72622ce0e2106aaa07bb4b2935ba2c370972 + FirebaseCoreDiagnostics: d50e11039e5984d92c8a512be2395f13df747350 + FirebaseDynamicLinks: a6df95d6dbc746c2bbf7d511343cda1344e2cf70 + FirebaseFirestore: 1906bf163afdb7c432d2e3b5c40ceb9dd2df5820 + FirebaseFunctions: 56b7275ad46d936b77b64ecacd306e77db7be251 + FirebaseInstallations: 7f7ed0e7e27fb51f57291e1876e2ddb1524126c1 + FirebaseInstanceID: aaecc93b4528bbcafea12c477e26827719ca1183 + FirebaseMessaging: 68d1bcb14880189558a8ae57167abe0b7e417232 + FirebaseStorage: 5002b1895bfe74a5ce92ad54f966e6162d0da2e5 + Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c + google_sign_in: 6bd214b9c154f881422f5fe27b66aaa7bbd580cc + GoogleAppMeasurement: 8d3c0aeede16ab7764144b5a4ca8e1d4323841b7 + GoogleDataTransport: 116c84c4bdeb76be2a7a46de51244368f9794eab + GoogleSignIn: 7137d297ddc022a7e0aa4619c86d72c909fa7213 + GoogleUtilities: abda45f519bc4073aa1171d2f8742108def46017 + "gRPC-C++": 13d8ccef97d5c3c441b7e3c529ef28ebee86fad2 + gRPC-Core: 4afa11bfbedf7cdecd04de535a9e046893404ed5 + GTMAppAuth: 197a8dabfea5d665224aa00d17f164fc2248dab9 + GTMSessionFetcher: b3503b20a988c4e20cc189aa798fd18220133f52 + image_picker: 50e7c7ff960e5f58faa4d1f4af84a771c671bc4a + leveldb-library: 50c7b45cbd7bf543c81a468fe557a16ae3db8729 + nanopb: 1bf24dd71191072e120b83dd02d08f3da0d65e53 + package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 + path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c + PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97 + share: 0b2c3e82132f5888bccca3351c504d0003b3b410 + shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d + sign_in_with_apple: 34f3f5456a45fd7ac5fb42905e2ad31dae061b4a + url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef + webview_flutter: 9f491a9b5a66f2573946a389b2677987b0ff8c0b + +PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c + +COCOAPODS: 1.10.1 diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..70271a3 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1130 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "14.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.41.2" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.5.0" + bloc: + dependency: "direct main" + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + bloc_test: + dependency: "direct dev" + description: + name: bloc_test + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.2" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.6" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.10" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.3" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.5" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.10" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.0.3" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" + clipboard: + dependency: "direct main" + description: + name: clipboard + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + cloud_functions: + dependency: "direct main" + description: + name: cloud_functions + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + cloud_functions_platform_interface: + dependency: transitive + description: + name: cloud_functions_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" + cloud_functions_web: + dependency: transitive + description: + name: cloud_functions_web + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + coverage: + dependency: transitive + description: + name: coverage + url: "https://pub.dartlang.org" + source: hosted + version: "0.15.2" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.2" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.12" + dartx: + dependency: "direct main" + description: + name: dartx + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0" + device_info: + dependency: "direct main" + description: + name: device_info + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.2+6" + easy_web_view: + dependency: "direct main" + description: + name: easy_web_view + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0-nullsafety" + email_validator: + dependency: "direct main" + description: + name: email_validator + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.6" + fake_async: + dependency: transitive + description: + name: fake_async + 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.0.0" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" + firebase: + dependency: transitive + description: + name: firebase + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.0" + firebase_analytics: + dependency: "direct main" + description: + name: firebase_analytics + url: "https://pub.dartlang.org" + source: hosted + version: "7.1.1" + firebase_analytics_platform_interface: + dependency: transitive + description: + name: firebase_analytics_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + firebase_analytics_web: + dependency: transitive + description: + name: firebase_analytics_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+1" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + firebase_dynamic_links: + dependency: "direct main" + description: + name: firebase_dynamic_links + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.0" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.0" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + firebase_storage: + dependency: "direct main" + description: + name: firebase_storage + url: "https://pub.dartlang.org" + source: hosted + version: "8.0.0" + firebase_storage_platform_interface: + dependency: transitive + description: + name: firebase_storage_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + firebase_storage_web: + dependency: transitive + description: + name: firebase_storage_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_markdown: + dependency: transitive + description: + name: flutter_markdown + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + flutter_signin_button: + dependency: "direct main" + description: + name: flutter_signin_button + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + font_awesome_flutter: + dependency: transitive + description: + name: font_awesome_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "8.12.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.0" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+4" + html2md: + dependency: transitive + description: + name: html2md + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.1" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + image_picker: + dependency: "direct main" + description: + name: image_picker + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.3" + image_picker_for_web: + dependency: "direct main" + description: + name: image_picker_for_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + url: "https://pub.dartlang.org" + source: hosted + version: "3.5.1" + kt_dart: + dependency: "direct main" + description: + name: kt_dart + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0+1" + logging: + dependency: "direct main" + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.4" + markdown: + dependency: transitive + description: + name: markdown + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.10" + material_design_icons_flutter: + dependency: "direct main" + description: + name: material_design_icons_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.5955" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7" + mockito: + dependency: transitive + description: + name: mockito + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.4" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.13" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + package_info: + dependency: "direct main" + description: + name: package_info + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.3+4" + path: + dependency: "direct main" + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + pool: + dependency: transitive + description: + name: pool + 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.1.0" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.3" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.8" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + rxdart: + dependency: "direct main" + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.23.1" + share: + dependency: "direct main" + description: + name: share + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.5+4" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + shelf_static: + dependency: transitive + description: + name: shelf_static + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.9+2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.4+1" + sign_in_with_apple: + dependency: "direct main" + description: + name: sign_in_with_apple + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.10+3" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + source_maps: + dependency: transitive + description: + name: source_maps + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.10" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test: + dependency: transitive + description: + name: test + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.5" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.19" + test_core: + dependency: transitive + description: + name: test_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.15" + time: + dependency: transitive + description: + name: time + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + timeago: + dependency: "direct main" + description: + name: timeago + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.30" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1+3" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.2" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + uuid: + dependency: "direct main" + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + vm_service: + dependency: transitive + description: + name: vm_service + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0+1" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.5" + webview_flutter: + dependency: "direct main" + description: + name: webview_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + wiredash: + dependency: "direct main" + description: + name: wiredash + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.2" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" +sdks: + dart: ">=2.12.0 <3.0.0" + flutter: ">=2.0.0" From 7c149c76562bd96e892eca0467b7955ec209529d Mon Sep 17 00:00:00 2001 From: Drew Heavner Date: Sun, 16 May 2021 20:18:34 -0400 Subject: [PATCH 66/67] removed web key file from git --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 61ef6d3..a0b2976 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ app.*.map.json # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages + +web/firebase-messaging-sw.js From a19435c88438c9c977f0b6a5e6e3b5778b20a46e Mon Sep 17 00:00:00 2001 From: Papa Daco Date: Mon, 2 May 2022 14:17:33 -0400 Subject: [PATCH 67/67] Version updates (e.g. dependencies, null safety, etc.) that is more ideal to start from --- importer/package-lock.json | 3277 ++++++++++++----- importer/package.json | 2 +- .../authentication_bloc.dart | 17 +- lib/data/app_preferences.dart | 100 +- .../cards/firestore_cards_repository.dart | 21 +- lib/data/features/cards/model/card_set.dart | 21 +- .../features/cards/model/prompt_card.dart | 15 +- .../features/cards/model/prompt_special.dart | 23 +- .../features/cards/model/response_card.dart | 12 +- .../game/firestore_game_repository.dart | 143 +- lib/data/features/game/game_repository.dart | 8 +- lib/data/features/game/model/game.dart | 48 +- lib/data/features/game/model/player.dart | 26 +- lib/data/features/game/model/turn.dart | 33 +- lib/data/features/game/model/turn_winner.dart | 43 +- lib/data/features/users/model/user.dart | 8 +- lib/data/features/users/model/user_game.dart | 15 +- lib/data/features/users/user_repository.dart | 85 +- lib/data/firestore.dart | 48 +- lib/internal/analytics.dart | 41 +- lib/internal/dynamic_links.dart | 101 +- lib/internal/localization.dart | 54 +- lib/internal/logging_bloc_delegate.dart | 32 +- lib/internal/push.dart | 55 +- lib/internal/theme.dart | 293 +- lib/l10n/messages_all.dart | 13 +- lib/main.dart | 7 +- lib/ui/creategame/bloc/create_game_bloc.dart | 60 +- lib/ui/creategame/bloc/create_game_state.dart | 53 +- lib/ui/creategame/create_game_screen.dart | 38 +- lib/ui/creategame/layouts/mobile_layout.dart | 38 +- lib/ui/creategame/layouts/tablet_layout.dart | 45 +- lib/ui/creategame/widgets/card_set_list.dart | 22 +- .../widgets/card_set_list_item.dart | 12 +- .../creategame/widgets/count_preference.dart | 20 +- lib/ui/creategame/widgets/game_options.dart | 20 +- lib/ui/creategame/widgets/header_item.dart | 20 +- lib/ui/game/bloc/game_bloc.dart | 87 +- lib/ui/game/bloc/game_event.dart | 9 +- lib/ui/game/bloc/game_state.dart | 97 +- lib/ui/game/game_screen.dart | 20 +- .../complete/completed_game_screen.dart | 46 +- .../screens/gameplay/game_play_screen.dart | 14 +- .../gameplay/widget/game_bottom_sheet.dart | 29 +- .../gameplay/widget/game_status_title.dart | 16 +- .../gameplay/widget/judge/judge_bar.dart | 21 +- .../gameplay/widget/judge/judge_dredd.dart | 62 +- .../gameplay/widget/judge/judging_pager.dart | 51 +- .../screens/gameplay/widget/player_item.dart | 19 +- .../screens/gameplay/widget/player_list.dart | 13 +- .../widget/player_response_picker.dart | 50 +- .../gameplay/widget/prompt_container.dart | 37 +- .../gameplay/widget/re_deal_button.dart | 34 +- .../gameplay/widget/response_card_view.dart | 16 +- .../widget/waiting_player_responses.dart | 12 +- .../widget/winning/other_responses_pager.dart | 12 +- .../widget/winning/prompt_card_view.dart | 33 +- .../widget/winning/turn_winner_sheet.dart | 35 +- .../starting/starting_room_screen.dart | 14 +- .../screens/waiting/waiting_room_screen.dart | 88 +- lib/ui/home/bloc/home_bloc.dart | 50 +- lib/ui/home/bloc/home_state.dart | 46 +- lib/ui/home/home_screen_v2.dart | 71 +- lib/ui/home/widgets/home_outline_button.dart | 12 +- lib/ui/home/widgets/join_game_widget.dart | 11 +- lib/ui/home/widgets/join_room_dialog.dart | 94 +- lib/ui/home/widgets/past_game.dart | 217 +- lib/ui/home/widgets/user_widget.dart | 22 +- lib/ui/profile/bloc/profile_bloc.dart | 79 +- lib/ui/profile/bloc/profile_event.dart | 4 +- lib/ui/profile/bloc/profile_state.dart | 10 +- lib/ui/profile/profile_screen.dart | 37 +- lib/ui/profile/widgets/profile_photo.dart | 35 +- lib/ui/routes.dart | 16 +- lib/ui/settings/settings_screen.dart | 120 +- .../widgets/frequent_tap_detector.dart | 14 +- lib/ui/settings/widgets/preference.dart | 27 +- .../settings/widgets/preference_header.dart | 7 +- lib/ui/settings/widgets/user_preference.dart | 32 +- lib/ui/signin/bloc/sign_in_bloc.dart | 20 +- lib/ui/signin/bloc/sign_in_state.dart | 15 +- lib/ui/signin/layouts/mobile_layout.dart | 7 +- lib/ui/signin/layouts/tablet_layout.dart | 15 +- lib/ui/signin/sign_in_screen.dart | 14 +- lib/ui/signin/widgets/apple_sign_in.dart | 2 +- lib/ui/signin/widgets/auth_button.dart | 21 +- lib/ui/signin/widgets/email_auth_form.dart | 33 +- lib/ui/terms_screen.dart | 30 +- lib/ui/widgets/player_circle_avatar.dart | 21 +- lib/ui/widgets/reponsive_widget_mediator.dart | 5 +- lib/ui/widgets/web_screen.dart | 4 +- pubspec.lock | 246 +- pubspec.yaml | 56 +- 93 files changed, 4471 insertions(+), 2606 deletions(-) diff --git a/importer/package-lock.json b/importer/package-lock.json index 53791a6..7001dcd 100644 --- a/importer/package-lock.json +++ b/importer/package-lock.json @@ -1,97 +1,1833 @@ { "name": "importer", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "importer", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@types/minimist": "^1.2.0", + "@types/node": "^13.11.1", + "firebase-admin": "^10.1.0", + "google-spreadsheet": "^3.0.11", + "minimist": "^1.2.5" + }, + "devDependencies": { + "typescript": "^3.8.3" + } + }, + "node_modules/@firebase/app": { + "version": "0.7.22", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.22.tgz", + "integrity": "sha512-v3AXSCwAvZyIFzOGgPAYtzjltm1M9R4U4yqsIBPf5B4ryaT1EGK+3ETZUOckNl5y2YwdKRJVPDDore+B2xg0Ug==", + "peer": true, + "dependencies": { + "@firebase/component": "0.5.13", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat": { + "version": "0.1.23", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.23.tgz", + "integrity": "sha512-c0QOhU2UVxZ7N5++nLQgKZ899ZC8+/ESa8VCzsQDwBw1T3MFAD1cG40KhB+CGtp/uYk/w6Jtk8k0xyZu6O2LOg==", + "peer": true, + "dependencies": { + "@firebase/app": "0.7.22", + "@firebase/component": "0.5.13", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", + "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.13.tgz", + "integrity": "sha512-hxhJtpD8Ppf/VU2Rlos6KFCEV77TGIGD5bJlkPK1+B/WUe0mC6dTjW7KhZtXTc+qRBp9nFHWcsIORnT8liHP9w==", + "dependencies": { + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "0.12.8", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.8.tgz", + "integrity": "sha512-JBQVfFLzfhxlQbl4OU6ov9fdsddkytBQdtSSR49cz48homj38ccltAhK6seum+BI7f28cV2LFHF9672lcN+qxA==", + "dependencies": { + "@firebase/auth-interop-types": "0.1.6", + "@firebase/component": "0.5.13", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.8.tgz", + "integrity": "sha512-dhXr5CSieBuKNdU96HgeewMQCT9EgOIkfF1GNy+iRrdl7BWLxmlKuvLfK319rmIytSs/vnCzcD9uqyxTeU/A3A==", + "dependencies": { + "@firebase/component": "0.5.13", + "@firebase/database": "0.12.8", + "@firebase/database-types": "0.9.7", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/database-types": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.7.tgz", + "integrity": "sha512-EFhgL89Fz6DY3kkB8TzdHvdu8XaqqvzcF2DLVOXEnQ3Ms7L755p5EO42LfxXoJqb9jKFvgLpFmKicyJG25WFWw==", + "dependencies": { + "@firebase/app-types": "0.7.0", + "@firebase/util": "1.5.2" + } + }, + "node_modules/@firebase/logger": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.5.2.tgz", + "integrity": "sha512-YvBH2UxFcdWG2HdFnhxZptPl2eVFlpOyTH66iDo13JPEYraWzWToZ5AMTtkyRHVmu7sssUpQlU9igy1KET7TOw==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@google-cloud/firestore": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", + "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^2.24.1", + "protobufjs": "^6.8.6" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", + "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", + "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", + "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/storage": { + "version": "5.19.4", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.19.4.tgz", + "integrity": "sha512-Jz7ugcPHhsEmMVvIxM9uoBsdEbKIYwDkh3u07tifsIymEWs47F4/D6+/Tv/W7kLhznqjyOjVJ/0frtBeIC0lJA==", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "abort-controller": "^3.0.0", + "arrify": "^2.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "configstore": "^5.0.0", + "date-and-time": "^2.0.0", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "get-stream": "^6.0.0", + "google-auth-library": "^7.14.1", + "hash-stream-validation": "^0.2.2", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "pumpify": "^2.0.0", + "retry-request": "^4.2.2", + "snakeize": "^0.1.0", + "stream-events": "^1.0.4", + "teeny-request": "^7.1.3", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", + "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.6.4", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.9.tgz", + "integrity": "sha512-UlcCS8VbsU9d3XTXGiEVFonN7hXk+oMXZtoHHG2oSA1/GcDP1q6OUgs20PzHDGizzyi8ufGSUDlk3O2NyY7leg==", + "optional": true, + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.10.0", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", + "optional": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "optional": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "optional": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", + "optional": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", + "optional": true + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", + "optional": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", + "optional": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", + "optional": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", + "optional": true + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-jwt": { + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", + "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", + "dependencies": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.28", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", + "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/express-unless": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.3.tgz", + "integrity": "sha512-TyPLQaF6w8UlWdv4gj8i46B+INBVzURBNRahCozCSXfsK2VTlL1wNyTlMKw817VHygBtlcl5jfnPadlydr06Yw==", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "optional": true + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "node_modules/@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=" + }, + "node_modules/@types/node": { + "version": "13.11.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.11.1.tgz", + "integrity": "sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/agent-base": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", + "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "node_modules/bignumber.js": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", + "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "optional": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "optional": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "optional": true, + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/date-and-time": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-2.3.1.tgz", + "integrity": "sha512-OaIRmSJXifwEN21rMVVDs0Kz8uhJ3wWPYd86atkRiqN54liaMQYEbbrgjZQea75YXOBWL4ZFb3rG/waenw1TEg==", + "optional": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dicer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", + "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "dependencies": { + "streamsearch": "0.1.2" + }, + "engines": { + "node": ">=4.5.0" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "optional": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "optional": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "optional": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "optional": true + }, + "node_modules/fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/firebase-admin": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-10.1.0.tgz", + "integrity": "sha512-4i4wu+EFgNfY4+D4DxXkZcmbD832ozUMNvHMkOFQrf8upyp51n6jrDJS+wLok9sd62yeqcImbnsLOympGlISPA==", + "dependencies": { + "@firebase/database-compat": "^0.1.1", + "@firebase/database-types": "^0.9.3", + "@types/node": ">=12.12.47", + "dicer": "^0.3.0", + "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^2.0.2", + "node-forge": "^1.3.1" + }, + "engines": { + "node": ">=12.7.0" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^4.15.1", + "@google-cloud/storage": "^5.18.3" + } + }, + "node_modules/follow-redirects": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "optional": true + }, + "node_modules/gaxios": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", + "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", + "dependencies": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gcp-metadata": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "dependencies": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "optional": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-auth-library": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", + "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-gax": { + "version": "2.30.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.2.tgz", + "integrity": "sha512-BCNCT26kb0iC52zj2SosyOZMhI5sVfXuul1h0Aw5uT9nGAbmS5eOvQ49ft53ft6XotDj11sUSDV6XESEiQqCqg==", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "~1.6.0", + "@grpc/proto-loader": "^0.6.1", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^7.14.0", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^0.1.8", + "protobufjs": "6.11.2", + "retry-request": "^4.0.0" + }, + "bin": { + "compileProtos": "build/tools/compileProtos.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-p12-pem": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", + "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-spreadsheet": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/google-spreadsheet/-/google-spreadsheet-3.2.0.tgz", + "integrity": "sha512-z7XMaqb+26rdo8p51r5O03u8aPLAPzn5YhOXYJPcf2hdMVr0dUbIARgdkRdmGiBeoV/QoU/7VNhq1MMCLZv3kQ==", + "dependencies": { + "axios": "^0.21.4", + "google-auth-library": "^6.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/google-spreadsheet/node_modules/google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "optional": true + }, + "node_modules/gtoken": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", + "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", + "dependencies": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hash-stream-validation": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", + "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", + "optional": true + }, + "node_modules/http-parser-js": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz", + "integrity": "sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "optional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "optional": true + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "optional": true + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "optional": true + }, + "node_modules/jose": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", + "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "dependencies": { + "@panva/asn1.js": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0 < 13 || >=13.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.1.0.tgz", + "integrity": "sha512-GKOSDBWWBCiQTzawei6mEdRQvji5gecj8F9JwMt0ZOPnBPSmTjo5CKFvvbhE7jGPkU159Cpi0+OTLuABFcNOQQ==", + "dependencies": { + "@types/express-jwt": "0.0.42", + "debug": "^4.3.4", + "jose": "^2.0.5", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "engines": { + "node": ">=10 < 13 || >=14" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "optional": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "optional": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "optional": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "optional": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proto3-json-serializer": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.8.tgz", + "integrity": "sha512-ACilkB6s1U1gWnl5jtICpnDai4VCxmI9GFxuEaYdxtDG2oVI3sVFIUsvUZcQbJgtPM6p+zqKbjTKQZp6Y4FpQw==", + "optional": true, + "dependencies": { + "protobufjs": "^6.11.2" + } + }, + "node_modules/protobufjs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "optional": true, + "dependencies": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", + "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", + "optional": true, + "dependencies": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "optional": true + }, + "node_modules/snakeize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", + "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", + "optional": true + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "optional": true + }, + "node_modules/streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", + "optional": true + }, + "node_modules/teeny-request": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz", + "integrity": "sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw==", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "optional": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "optional": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "optional": true + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "optional": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, "dependencies": { + "@firebase/app": { + "version": "0.7.22", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.22.tgz", + "integrity": "sha512-v3AXSCwAvZyIFzOGgPAYtzjltm1M9R4U4yqsIBPf5B4ryaT1EGK+3ETZUOckNl5y2YwdKRJVPDDore+B2xg0Ug==", + "peer": true, + "requires": { + "@firebase/component": "0.5.13", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, + "@firebase/app-compat": { + "version": "0.1.23", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.23.tgz", + "integrity": "sha512-c0QOhU2UVxZ7N5++nLQgKZ899ZC8+/ESa8VCzsQDwBw1T3MFAD1cG40KhB+CGtp/uYk/w6Jtk8k0xyZu6O2LOg==", + "peer": true, + "requires": { + "@firebase/app": "0.7.22", + "@firebase/component": "0.5.13", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, "@firebase/app-types": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.0.tgz", - "integrity": "sha512-ld6rzjXk/SUauHiQZJkeuSJpxIZ5wdnWuF5fWBFQNPaxsaJ9kyYg9GqEvwZ1z2e6JP5cU9gwRBlfW1WkGtGDYA==" + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", + "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==" }, "@firebase/auth-interop-types": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.4.tgz", - "integrity": "sha512-CLKNS84KGAv5lRnHTQZFWoR11Ti7gIPFirDDXWek/fSU+TdYdnxJFR5XSD4OuGyzUYQ3Dq7aVj5teiRdyBl9hA==" + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "requires": {} }, "@firebase/component": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.9.tgz", - "integrity": "sha512-i58GsVpxBGnKn1rx2RCAH0rk1Ldp6WterfBNDHyxmuyRO6BaZAgvxrZ3Ku1/lqiI7XMbmmRpP3emmwrStbFt9Q==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.13.tgz", + "integrity": "sha512-hxhJtpD8Ppf/VU2Rlos6KFCEV77TGIGD5bJlkPK1+B/WUe0mC6dTjW7KhZtXTc+qRBp9nFHWcsIORnT8liHP9w==", "requires": { - "@firebase/util": "0.2.44", - "tslib": "1.11.1" + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" } }, "@firebase/database": { - "version": "0.5.25", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.5.25.tgz", - "integrity": "sha512-qUIpgDoODWs/FEdCQoH/VwRDvW7nn7m99TGxbMhdiE2WV/nzKbCo/PbbGm0dltdZzQ/SE87E2lfpPGK89Riw6Q==", + "version": "0.12.8", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.8.tgz", + "integrity": "sha512-JBQVfFLzfhxlQbl4OU6ov9fdsddkytBQdtSSR49cz48homj38ccltAhK6seum+BI7f28cV2LFHF9672lcN+qxA==", "requires": { - "@firebase/auth-interop-types": "0.1.4", - "@firebase/component": "0.1.9", - "@firebase/database-types": "0.4.14", - "@firebase/logger": "0.2.1", - "@firebase/util": "0.2.44", - "faye-websocket": "0.11.3", - "tslib": "1.11.1" + "@firebase/auth-interop-types": "0.1.6", + "@firebase/component": "0.5.13", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "@firebase/database-compat": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.8.tgz", + "integrity": "sha512-dhXr5CSieBuKNdU96HgeewMQCT9EgOIkfF1GNy+iRrdl7BWLxmlKuvLfK319rmIytSs/vnCzcD9uqyxTeU/A3A==", + "requires": { + "@firebase/component": "0.5.13", + "@firebase/database": "0.12.8", + "@firebase/database-types": "0.9.7", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" } }, "@firebase/database-types": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.4.14.tgz", - "integrity": "sha512-+D41HWac0HcvwMi+0dezEdSOZHpVjPKPNmpQiW2GDuS5kk27/v1jxc9v7F4ALLtpxbVcn16UZl5PqEkcS9H2Xg==", + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.7.tgz", + "integrity": "sha512-EFhgL89Fz6DY3kkB8TzdHvdu8XaqqvzcF2DLVOXEnQ3Ms7L755p5EO42LfxXoJqb9jKFvgLpFmKicyJG25WFWw==", "requires": { - "@firebase/app-types": "0.6.0" + "@firebase/app-types": "0.7.0", + "@firebase/util": "1.5.2" } }, "@firebase/logger": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.1.tgz", - "integrity": "sha512-H4nttTqUzEw3TA/JYl8ma6oMSNKHcdpEWV2L2qA+ZEcpM2OLAzagi//DrYBFR5xpPb17IGagpzSxFgx937Sq/A==" - }, - "@firebase/util": { - "version": "0.2.44", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.44.tgz", - "integrity": "sha512-yWnFdeuz7P0QC4oC77JyPdAQ/rTGPDfhHcR5WsoMsKBBHTyqEhaKWL9HeRird+p3AL9M4++ep0FYFNd1UKU3Wg==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", "requires": { - "tslib": "1.11.1" + "tslib": "^2.1.0" } }, - "@google-cloud/common": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", - "integrity": "sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg==", - "optional": true, + "@firebase/util": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.5.2.tgz", + "integrity": "sha512-YvBH2UxFcdWG2HdFnhxZptPl2eVFlpOyTH66iDo13JPEYraWzWToZ5AMTtkyRHVmu7sssUpQlU9igy1KET7TOw==", "requires": { - "@google-cloud/projectify": "^1.0.0", - "@google-cloud/promisify": "^1.0.0", - "arrify": "^2.0.0", - "duplexify": "^3.6.0", - "ent": "^2.2.0", - "extend": "^3.0.2", - "google-auth-library": "^5.5.0", - "retry-request": "^4.0.0", - "teeny-request": "^6.0.0" + "tslib": "^2.1.0" } }, "@google-cloud/firestore": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-3.7.4.tgz", - "integrity": "sha512-RBMG4uZFHeQPFMHTRFMyQ7LDQTLa0f+U0hLAa/7XWjpZHgxKuOWBonsv+C3geymAwShIZSoV/NpNh9tBK7YF5g==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", + "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", "optional": true, "requires": { - "deep-equal": "^2.0.0", + "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^1.13.0", - "readable-stream": "^3.4.0", - "through2": "^3.0.0" + "google-gax": "^2.24.1", + "protobufjs": "^6.8.6" } }, "@google-cloud/paginator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz", - "integrity": "sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", + "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", "optional": true, "requires": { "arrify": "^2.0.0", @@ -99,81 +1835,78 @@ } }, "@google-cloud/projectify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz", - "integrity": "sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", + "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", "optional": true }, "@google-cloud/promisify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz", - "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", + "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", "optional": true }, "@google-cloud/storage": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-4.7.0.tgz", - "integrity": "sha512-f0guAlbeg7Z0m3gKjCfBCu7FG9qS3M3oL5OQQxlvGoPtK7/qg3+W+KQV73O2/sbuS54n0Kh2mvT5K2FWzF5vVQ==", + "version": "5.19.4", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.19.4.tgz", + "integrity": "sha512-Jz7ugcPHhsEmMVvIxM9uoBsdEbKIYwDkh3u07tifsIymEWs47F4/D6+/Tv/W7kLhznqjyOjVJ/0frtBeIC0lJA==", "optional": true, "requires": { - "@google-cloud/common": "^2.1.1", - "@google-cloud/paginator": "^2.0.0", - "@google-cloud/promisify": "^1.0.0", + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "abort-controller": "^3.0.0", "arrify": "^2.0.0", + "async-retry": "^1.3.3", "compressible": "^2.0.12", - "concat-stream": "^2.0.0", - "date-and-time": "^0.13.0", - "duplexify": "^3.5.0", + "configstore": "^5.0.0", + "date-and-time": "^2.0.0", + "duplexify": "^4.0.0", + "ent": "^2.2.0", "extend": "^3.0.2", - "gaxios": "^3.0.0", - "gcs-resumable-upload": "^2.2.4", + "gaxios": "^4.0.0", + "get-stream": "^6.0.0", + "google-auth-library": "^7.14.1", "hash-stream-validation": "^0.2.2", - "mime": "^2.2.0", + "mime": "^3.0.0", "mime-types": "^2.0.8", - "onetime": "^5.1.0", - "p-limit": "^2.2.0", + "p-limit": "^3.0.1", "pumpify": "^2.0.0", - "readable-stream": "^3.4.0", + "retry-request": "^4.2.2", "snakeize": "^0.1.0", - "stream-events": "^1.0.1", - "through2": "^3.0.0", + "stream-events": "^1.0.4", + "teeny-request": "^7.1.3", "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "gaxios": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.2.tgz", - "integrity": "sha512-cLOetrsKOBLPwjzVyFzirYaGjrhtYjbKUHp6fQpsio2HH8Mil35JTFQLgkV5D3CCXV7Gnd5V69/m4C9rMBi9bA==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" - } - } } }, "@grpc/grpc-js": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-0.7.9.tgz", - "integrity": "sha512-ihn9xWOqubMPBlU77wcYpy7FFamGo5xtsK27EAILL/eoOvGEAq29UOrqRvqYPwWfl2+3laFmGKNR7uCdJhKu4Q==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", + "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", "optional": true, "requires": { - "semver": "^6.2.0" + "@grpc/proto-loader": "^0.6.4", + "@types/node": ">=12.12.47" } }, "@grpc/proto-loader": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.4.tgz", - "integrity": "sha512-HTM4QpI9B2XFkPz7pjwMyMgZchJ93TVkL3kWPW8GDMDKYxsMnmf4w2TNMJK7+KNiYHS5cJrCEAFlF+AwtXWVPA==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.9.tgz", + "integrity": "sha512-UlcCS8VbsU9d3XTXGiEVFonN7hXk+oMXZtoHHG2oSA1/GcDP1q6OUgs20PzHDGizzyi8ufGSUDlk3O2NyY7leg==", "optional": true, "requires": { + "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" + "long": "^4.0.0", + "protobufjs": "^6.10.0", + "yargs": "^16.2.0" } }, + "@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -239,26 +1972,77 @@ "optional": true }, "@tootallnate/once": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", - "integrity": "sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "optional": true }, - "@types/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-UoOfVEzAUpeSPmjm7h1uk5MH6KZma2z2O7a75onTGjnNvAvMVrPzPL/vBbT65iIGHWj6rokwfmYcmxmlSf2uwg==", - "optional": true, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", "requires": { "@types/node": "*" } }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-jwt": { + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", + "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", + "requires": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.28", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", + "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/express-unless": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.3.tgz", + "integrity": "sha512-TyPLQaF6w8UlWdv4gj8i46B+INBVzURBNRahCozCSXfsK2VTlL1wNyTlMKw817VHygBtlcl5jfnPadlydr06Yw==", + "requires": { + "@types/express": "*" + } + }, "@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", "optional": true }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, "@types/minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", @@ -269,6 +2053,25 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.11.1.tgz", "integrity": "sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==" }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -281,37 +2084,45 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", - "optional": true, "requires": { "debug": "4" } }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "optional": true }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" }, - "available-typed-arrays": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", - "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", + "async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", "optional": true, "requires": { - "array-filter": "^1.0.0" + "retry": "0.13.1" } }, "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", "requires": { - "follow-redirects": "1.5.10" + "follow-redirects": "^1.14.0" } }, "base64-js": { @@ -320,19 +2131,39 @@ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "bignumber.js": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", - "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", + "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==" }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "optional": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "optional": true }, "compressible": { @@ -344,18 +2175,6 @@ "mime-db": ">= 1.43.0 < 2" } }, - "concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "optional": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, "configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -370,12 +2189,6 @@ "xdg-basedir": "^4.0.0" } }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "optional": true - }, "crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -383,47 +2196,17 @@ "optional": true }, "date-and-time": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.13.1.tgz", - "integrity": "sha512-/Uge9DJAT+s+oAcDxtBhyR8+sKjUnZbYmyhbmWjTHNtX7B7oWD8YyYdeXcBRbwSj6hVvj+IQegJam7m7czhbFw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-2.3.1.tgz", + "integrity": "sha512-OaIRmSJXifwEN21rMVVDs0Kz8uhJ3wWPYd86atkRiqN54liaMQYEbbrgjZQea75YXOBWL4ZFb3rG/waenw1TEg==", "optional": true }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-equal": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.2.tgz", - "integrity": "sha512-kX0bjV7tdMuhrhzKPEnVwqfQCuf+IEfN+4Xqv4eKd75xGRyn8yzdQ9ujPY6a221rgJKyQC4KBu1PibDTpa6m9w==", - "optional": true, - "requires": { - "es-abstract": "^1.17.5", - "es-get-iterator": "^1.1.0", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.0.5", - "isarray": "^2.0.5", - "object-is": "^1.0.2", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.2", - "which-boxed-primitive": "^1.0.1", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.1" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "object-keys": "^1.0.12" + "ms": "2.1.2" } }, "dicer": { @@ -435,53 +2218,24 @@ } }, "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "optional": true, "requires": { "is-obj": "^2.0.0" } }, "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", "optional": true, "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", "stream-shift": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - } } }, "ecdsa-sig-formatter": { @@ -492,6 +2246,12 @@ "safe-buffer": "^5.0.1" } }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "optional": true + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -507,48 +2267,11 @@ "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", "optional": true }, - "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-get-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", - "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", - "optional": true, - "requires": { - "es-abstract": "^1.17.4", - "has-symbols": "^1.0.1", - "is-arguments": "^1.0.4", - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "optional": true }, "event-target-shim": { "version": "5.0.1", @@ -560,73 +2283,45 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "optional": true + }, "fast-text-encoding": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.1.tgz", - "integrity": "sha512-x4FEgaz3zNRtJfLFqJmHWxkMDDvXVtaznj2V9jiP8ACUJrUgist4bP9FmDL2Vew2Y9mEQI/tG4GqabaitYp9CQ==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" }, "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", "requires": { "websocket-driver": ">=0.5.1" } }, "firebase-admin": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-8.10.0.tgz", - "integrity": "sha512-QzJZ1sBh9xzKjb44aP6m1duy0Xe1ixexwh0eaOt1CkJYCOq2b6bievK4GNWMl5yGQ7FFBEbZO6hyDi+5wrctcg==", - "requires": { - "@firebase/database": "^0.5.17", - "@google-cloud/firestore": "^3.0.0", - "@google-cloud/storage": "^4.1.2", - "@types/node": "^8.10.59", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-10.1.0.tgz", + "integrity": "sha512-4i4wu+EFgNfY4+D4DxXkZcmbD832ozUMNvHMkOFQrf8upyp51n6jrDJS+wLok9sd62yeqcImbnsLOympGlISPA==", + "requires": { + "@firebase/database-compat": "^0.1.1", + "@firebase/database-types": "^0.9.3", + "@google-cloud/firestore": "^4.15.1", + "@google-cloud/storage": "^5.18.3", + "@types/node": ">=12.12.47", "dicer": "^0.3.0", - "jsonwebtoken": "8.1.0", - "node-forge": "0.7.4" - }, - "dependencies": { - "@types/node": { - "version": "8.10.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.60.tgz", - "integrity": "sha512-YjPbypHFuiOV0bTgeF07HpEEqhmHaZqYNSdCKeBJa+yFoQ/7BC+FpJcwmi34xUIIRVFktnUyP1dPU8U0612GOg==" - } - } - }, - "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "optional": true + "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^2.0.2", + "node-forge": "^1.3.1" + } }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "follow-redirects": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -635,307 +2330,146 @@ "optional": true }, "gaxios": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", - "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", - "optional": true, + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", + "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", "requires": { "abort-controller": "^3.0.0", "extend": "^3.0.2", "https-proxy-agent": "^5.0.0", "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" + "node-fetch": "^2.6.7" } }, "gcp-metadata": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz", - "integrity": "sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA==", - "optional": true, + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", "requires": { - "gaxios": "^2.1.0", - "json-bigint": "^0.3.0" + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" } }, - "gcs-resumable-upload": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-2.3.3.tgz", - "integrity": "sha512-sf896I5CC/1AxeaGfSFg3vKMjUq/r+A3bscmVzZm10CElyRanN0XwPu/MxeIO4LSP+9uF6yKzXvNsaTsMXUG6Q==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "configstore": "^5.0.0", - "gaxios": "^2.0.0", - "google-auth-library": "^5.0.0", - "pumpify": "^2.0.0", - "stream-events": "^1.0.4" - } + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "optional": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "optional": true }, "google-auth-library": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", - "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", + "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", "optional": true, "requires": { "arrify": "^2.0.0", "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "fast-text-encoding": "^1.0.0", - "gaxios": "^2.1.0", - "gcp-metadata": "^3.4.0", - "gtoken": "^4.1.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", "jws": "^4.0.0", - "lru-cache": "^5.0.0" + "lru-cache": "^6.0.0" } }, "google-gax": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-1.15.2.tgz", - "integrity": "sha512-yNNiRf9QxWpZNfQQmSPz3rIDTBDDKnLKY/QEsjCaJyDxttespr6v8WRGgU5KrU/6ZM7QRlgBAYXCkxqHhJp0wA==", + "version": "2.30.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.2.tgz", + "integrity": "sha512-BCNCT26kb0iC52zj2SosyOZMhI5sVfXuul1h0Aw5uT9nGAbmS5eOvQ49ft53ft6XotDj11sUSDV6XESEiQqCqg==", "optional": true, "requires": { - "@grpc/grpc-js": "^0.7.4", - "@grpc/proto-loader": "^0.5.1", - "@types/fs-extra": "^8.0.1", + "@grpc/grpc-js": "~1.6.0", + "@grpc/proto-loader": "^0.6.1", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", - "duplexify": "^3.6.0", - "google-auth-library": "^5.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^7.14.0", "is-stream-ended": "^0.1.4", - "lodash.at": "^4.6.0", - "lodash.has": "^4.5.2", - "node-fetch": "^2.6.0", - "protobufjs": "^6.8.9", - "retry-request": "^4.0.0", - "semver": "^6.0.0", - "walkdir": "^0.4.0" + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^0.1.8", + "protobufjs": "6.11.2", + "retry-request": "^4.0.0" } }, "google-p12-pem": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz", - "integrity": "sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg==", - "optional": true, + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", + "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", "requires": { - "node-forge": "^0.9.0" - }, - "dependencies": { - "node-forge": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", - "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==", - "optional": true - } + "node-forge": "^1.3.1" } }, "google-spreadsheet": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/google-spreadsheet/-/google-spreadsheet-3.0.11.tgz", - "integrity": "sha512-bkYUdsq4Nwg7klnFevG6MRHXAfGh9gYVymGp001YdtThct5uSIB/41lUOsGzFbQQSR5FcBTwAu9nZVYvaNdv9Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/google-spreadsheet/-/google-spreadsheet-3.2.0.tgz", + "integrity": "sha512-z7XMaqb+26rdo8p51r5O03u8aPLAPzn5YhOXYJPcf2hdMVr0dUbIARgdkRdmGiBeoV/QoU/7VNhq1MMCLZv3kQ==", "requires": { - "axios": "^0.19.1", - "google-auth-library": "^5.9.1", - "lodash": "^4.17.15" + "axios": "^0.21.4", + "google-auth-library": "^6.1.3", + "lodash": "^4.17.21" }, "dependencies": { - "agent-base": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", - "requires": { - "debug": "4" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "gaxios": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", - "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" - } - }, - "gcp-metadata": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz", - "integrity": "sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA==", - "requires": { - "gaxios": "^2.1.0", - "json-bigint": "^0.3.0" - } - }, "google-auth-library": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", - "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", "requires": { "arrify": "^2.0.0", "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "fast-text-encoding": "^1.0.0", - "gaxios": "^2.1.0", - "gcp-metadata": "^3.4.0", - "gtoken": "^4.1.0", - "jws": "^4.0.0", - "lru-cache": "^5.0.0" - } - }, - "google-p12-pem": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz", - "integrity": "sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg==", - "requires": { - "node-forge": "^0.9.0" - } - }, - "gtoken": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz", - "integrity": "sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA==", - "requires": { - "gaxios": "^2.1.0", - "google-p12-pem": "^2.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", "jws": "^4.0.0", - "mime": "^2.2.0" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" + "lru-cache": "^6.0.0" } - }, - "node-forge": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", - "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==" } } }, "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "optional": true }, "gtoken": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz", - "integrity": "sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA==", - "optional": true, - "requires": { - "gaxios": "^2.1.0", - "google-p12-pem": "^2.0.0", - "jws": "^4.0.0", - "mime": "^2.2.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", + "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", "requires": { - "function-bind": "^1.1.1" + "gaxios": "^4.0.0", + "google-p12-pem": "^3.1.3", + "jws": "^4.0.0" } }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" - }, "hash-stream-validation": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.2.tgz", - "integrity": "sha512-cMlva5CxWZOrlS/cY0C+9qAzesn5srhFA8IT1VPiHc9bWWBLkJfEUIZr7MWoi89oOOGmpg8ymchaOjiArsGu5A==", - "optional": true, - "requires": { - "through2": "^2.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "optional": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", + "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", + "optional": true }, "http-parser-js": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=" + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz", + "integrity": "sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==" }, "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "optional": true, "requires": { - "@tootallnate/once": "1", + "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } @@ -944,7 +2478,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "optional": true, "requires": { "agent-base": "6", "debug": "4" @@ -962,44 +2495,10 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "optional": true }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "optional": true - }, - "is-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", - "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==", - "optional": true - }, - "is-boolean-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", - "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", - "optional": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" - }, - "is-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", - "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", - "optional": true - }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "optional": true }, "is-obj": { @@ -1008,20 +2507,6 @@ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "optional": true }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "requires": { - "has": "^1.0.3" - } - }, - "is-set": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", - "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", - "optional": true - }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -1033,70 +2518,34 @@ "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", "optional": true }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "optional": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typed-array": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", - "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", - "optional": true, - "requires": { - "available-typed-arrays": "^1.0.0", - "es-abstract": "^1.17.4", - "foreach": "^2.0.5", - "has-symbols": "^1.0.1" - } - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "optional": true }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "optional": true - }, - "is-weakset": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", - "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", - "optional": true - }, - "isarray": { + "jose": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "optional": true + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", + "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "requires": { + "@panva/asn1.js": "^1.0.0" + } }, "json-bigint": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", - "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "requires": { - "bignumber.js": "^7.0.0" + "bignumber.js": "^9.0.0" } }, "jsonwebtoken": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.1.0.tgz", - "integrity": "sha1-xjl80uX9WD1lwAeoPce7eOaYK4M=", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", "requires": { - "jws": "^3.1.4", + "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -1104,8 +2553,8 @@ "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", - "ms": "^2.0.0", - "xtend": "^4.0.1" + "ms": "^2.1.1", + "semver": "^5.6.0" }, "dependencies": { "jwa": { @@ -1126,6 +2575,11 @@ "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -1133,33 +2587,42 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "optional": true, "requires": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, + "jwks-rsa": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.1.0.tgz", + "integrity": "sha512-GKOSDBWWBCiQTzawei6mEdRQvji5gecj8F9JwMt0ZOPnBPSmTjo5CKFvvbhE7jGPkU159Cpi0+OTLuABFcNOQQ==", + "requires": { + "@types/express-jwt": "0.0.42", + "debug": "^4.3.4", + "jose": "^2.0.5", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + } + }, "jws": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "optional": true, "requires": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, - "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, - "lodash.at": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz", - "integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g=", - "optional": true + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.camelcase": { "version": "4.3.0", @@ -1167,11 +2630,10 @@ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "optional": true }, - "lodash.has": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=", - "optional": true + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, "lodash.includes": { "version": "4.3.0", @@ -1215,52 +2677,72 @@ "optional": true }, "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", "requires": { - "yallist": "^3.0.2" + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "requires": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } } }, "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "optional": true, "requires": { "semver": "^6.0.0" } }, "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "optional": true }, "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "optional": true }, "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "optional": true, "requires": { - "mime-db": "1.43.0" + "mime-db": "1.52.0" } }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "optional": true - }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "ms": { "version": "2.1.2", @@ -1268,42 +2750,24 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } }, "node-forge": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.4.tgz", - "integrity": "sha512-8Df0906+tq/omxuCZD6PqhPaQDYuyJ1d+VITgxoIA8zvQd1ru+nMJcDChHH324MWitIgbVkAkQoGEEVJNpn/PA==" - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" }, - "object-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", - "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "optional": true }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1313,40 +2777,28 @@ "wrappy": "1" } }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "optional": true, "requires": { - "mimic-fn": "^2.1.0" + "yocto-queue": "^0.1.0" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "proto3-json-serializer": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.8.tgz", + "integrity": "sha512-ACilkB6s1U1gWnl5jtICpnDai4VCxmI9GFxuEaYdxtDG2oVI3sVFIUsvUZcQbJgtPM6p+zqKbjTKQZp6Y4FpQw==", "optional": true, "requires": { - "p-try": "^2.0.0" + "protobufjs": "^6.11.2" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "optional": true - }, "protobufjs": { - "version": "6.8.9", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.9.tgz", - "integrity": "sha512-j2JlRdUeL/f4Z6x4aU4gj9I2LECglC+5qR2TrWb193Tla1qfdaNQTZ8I27Pt7K0Ajmvjjpft7O3KWTGciz4gpw==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", "optional": true, "requires": { "@protobufjs/aspromise": "^1.1.2", @@ -1359,19 +2811,16 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.0", - "@types/node": "^10.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", "long": "^4.0.0" - }, - "dependencies": { - "@types/node": { - "version": "10.17.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.19.tgz", - "integrity": "sha512-46/xThm3zvvc9t9/7M3AaLEqtOpqlYYYcCZbpYVAQHG20+oMZBkae/VMrn4BTi6AJ8cpack0mEXhGiKmDNbLrQ==", - "optional": true - } } }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1391,20 +2840,6 @@ "duplexify": "^4.1.1", "inherits": "^2.0.3", "pump": "^3.0.0" - }, - "dependencies": { - "duplexify": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", - "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", - "optional": true, - "requires": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - } } }, "readable-stream": { @@ -1418,24 +2853,26 @@ "util-deprecate": "^1.0.1" } }, - "regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", - "optional": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "optional": true + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "optional": true }, "retry-request": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.1.tgz", - "integrity": "sha512-BINDzVtLI2BDukjWmjAIRZ0oglnCAkpP2vQjM3jdLhmT62h0xnQgciPwBRDAvHqpkPT2Wo1XuUyLyn6nbGrZQQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", + "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", "optional": true, "requires": { "debug": "^4.1.1", - "through2": "^3.0.1" + "extend": "^3.0.2" } }, "safe-buffer": { @@ -1449,20 +2886,10 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "optional": true }, - "side-channel": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", - "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", - "optional": true, - "requires": { - "es-abstract": "^1.17.0-next.1", - "object-inspect": "^1.7.0" - } - }, "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "optional": true }, "snakeize": { @@ -1491,59 +2918,33 @@ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" + "safe-buffer": "~5.2.0" } }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "optional": true, "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - } + "ansi-regex": "^5.0.1" } }, "stubs": { @@ -1553,37 +2954,27 @@ "optional": true }, "teeny-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.3.tgz", - "integrity": "sha512-TZG/dfd2r6yeji19es1cUIwAlVD8y+/svB1kAC2Y0bjEyysrfbO8EZvJBRwIE6WkwmUoB7uvWLwTIhJbMXZ1Dw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz", + "integrity": "sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw==", "optional": true, "requires": { - "http-proxy-agent": "^4.0.0", + "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.2.0", + "node-fetch": "^2.6.1", "stream-events": "^1.0.5", - "uuid": "^7.0.0" + "uuid": "^8.0.0" } }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "optional": true, - "requires": { - "readable-stream": "2 || 3" - } + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "optional": true + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "typedarray-to-buffer": { "version": "3.1.5", @@ -1616,23 +3007,22 @@ "optional": true }, "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "optional": true }, - "walkdir": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", - "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", - "optional": true + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "websocket-driver": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", - "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "requires": { - "http-parser-js": ">=0.4.0 <0.4.11", + "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" } @@ -1642,43 +3032,24 @@ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" }, - "which-boxed-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", - "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", - "optional": true, - "requires": { - "is-bigint": "^1.0.0", - "is-boolean-object": "^1.0.0", - "is-number-object": "^1.0.3", - "is-string": "^1.0.4", - "is-symbol": "^1.0.2" - } - }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "optional": true, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, - "which-typed-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", - "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "optional": true, "requires": { - "available-typed-arrays": "^1.0.2", - "es-abstract": "^1.17.5", - "foreach": "^2.0.5", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.1", - "is-typed-array": "^1.1.3" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, "wrappy": { @@ -1705,15 +3076,43 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "optional": true }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "optional": true }, "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "optional": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "optional": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "optional": true } } } diff --git a/importer/package.json b/importer/package.json index 7c1f68c..3f56995 100644 --- a/importer/package.json +++ b/importer/package.json @@ -18,7 +18,7 @@ "dependencies": { "@types/minimist": "^1.2.0", "@types/node": "^13.11.1", - "firebase-admin": "^8.10.0", + "firebase-admin": "^10.1.0", "google-spreadsheet": "^3.0.11", "minimist": "^1.2.5" } diff --git a/lib/authentication_bloc/authentication_bloc.dart b/lib/authentication_bloc/authentication_bloc.dart index 475a2a8..aff5697 100644 --- a/lib/authentication_bloc/authentication_bloc.dart +++ b/lib/authentication_bloc/authentication_bloc.dart @@ -6,21 +6,22 @@ import 'package:appsagainsthumanity/internal.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:logging/logging.dart'; -import 'package:meta/meta.dart'; +// import 'package:meta/meta.dart'; part 'authentication_event.dart'; - part 'authentication_state.dart'; -class AuthenticationBloc extends Bloc { +class AuthenticationBloc + extends Bloc { final UserRepository _userRepository; final AppPreferences _preferences; - AuthenticationBloc({@required UserRepository userRepository, @required AppPreferences preferences}) - : assert(userRepository != null), - assert(preferences != null), - _userRepository = userRepository, - _preferences = preferences; + AuthenticationBloc({ + required UserRepository userRepository, + required AppPreferences preferences, + }) : _userRepository = userRepository, + _preferences = preferences, + super(Uninitialized()) {} @override AuthenticationState get initialState => Uninitialized(); diff --git a/lib/data/app_preferences.dart b/lib/data/app_preferences.dart index aacae7f..112951b 100644 --- a/lib/data/app_preferences.dart +++ b/lib/data/app_preferences.dart @@ -2,66 +2,70 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:uuid/uuid.dart'; class AppPreferences { - static AppPreferences _instance = AppPreferences._internal(); + static AppPreferences _instance = AppPreferences._internal(); - static Future loadInstance() async { - if (_instance._prefs == null) { - _instance._prefs = await SharedPreferences.getInstance(); - } - return _instance; + static Future loadInstance() async { + if (_instance._prefs == {}) { + _instance._prefs = await SharedPreferences.getInstance(); } + return _instance; + } - static const KEY_AGREE_TO_TERMS = "agree_to_terms_of_service"; - static const KEY_PUSH_TOKEN = "push_token"; - static const KEY_DEVICE_ID = "device_id"; - static const KEY_PRIZES_TO_WIN = "prizes_to_win"; - static const KEY_PLAYER_LIMIT = "player_limit"; - static const KEY_DEVELOPER_PACKS = "developer_packs"; + static const KEY_AGREE_TO_TERMS = "agree_to_terms_of_service"; + static const KEY_PUSH_TOKEN = "push_token"; + static const KEY_DEVICE_ID = "device_id"; + static const KEY_PRIZES_TO_WIN = "prizes_to_win"; + static const KEY_PLAYER_LIMIT = "player_limit"; + static const KEY_DEVELOPER_PACKS = "developer_packs"; - SharedPreferences _prefs; + SharedPreferences _prefs; - AppPreferences._internal(); + AppPreferences._internal(); - factory AppPreferences() { - return _instance; - } + factory AppPreferences() { + return _instance; + } - /// Whether or not the user has agreed to TOS - bool get agreedToTermsOfService => _prefs.getBool(KEY_AGREE_TO_TERMS) ?? false; - set agreedToTermsOfService(bool agreed) => _prefs.setBool(KEY_AGREE_TO_TERMS, agreed); + /// Whether or not the user has agreed to TOS + bool get agreedToTermsOfService => + _prefs.getBool(KEY_AGREE_TO_TERMS) ?? false; + set agreedToTermsOfService(bool agreed) => + _prefs.setBool(KEY_AGREE_TO_TERMS, agreed); - /// The Push Token - String get pushToken => _prefs.getString(KEY_PUSH_TOKEN); - set pushToken(String token) => _prefs.setString(KEY_PUSH_TOKEN, token); + /// The Push Token + String get pushToken => _prefs.getString(KEY_PUSH_TOKEN); + set pushToken(String token) => _prefs.setString(KEY_PUSH_TOKEN, token); - /// The last used # of prizes to win - int get prizesToWin => _prefs.getInt(KEY_PRIZES_TO_WIN) ?? 7; - set prizesToWin(int value) => _prefs.setInt(KEY_PRIZES_TO_WIN, value); + /// The last used # of prizes to win + int get prizesToWin => _prefs.getInt(KEY_PRIZES_TO_WIN) ?? 7; + set prizesToWin(int value) => _prefs.setInt(KEY_PRIZES_TO_WIN, value); - /// The last used player limit of new games - int get playerLimit => _prefs.getInt(KEY_PLAYER_LIMIT) ?? 15; - set playerLimit(int value) => _prefs.setInt(KEY_PLAYER_LIMIT, value); + /// The last used player limit of new games + int get playerLimit => _prefs.getInt(KEY_PLAYER_LIMIT) ?? 15; + set playerLimit(int value) => _prefs.setInt(KEY_PLAYER_LIMIT, value); - /// Whether or not the user has unlocked the developer pack(s) - bool get developerPackEnabled => _prefs.getBool(KEY_DEVELOPER_PACKS) ?? false; - set developerPackEnabled(bool enabled) => _prefs.setBool(KEY_DEVELOPER_PACKS, enabled); + /// Whether or not the user has unlocked the developer pack(s) + bool get developerPackEnabled => _prefs.getBool(KEY_DEVELOPER_PACKS) ?? false; + set developerPackEnabled(bool enabled) => + _prefs.setBool(KEY_DEVELOPER_PACKS, enabled); - /// The persistent device id - String get deviceId { - var dId = _prefs.getString(KEY_DEVICE_ID); - if (dId == null) { - dId = Uuid().v4(); - deviceId = dId; - } - return dId; + /// The persistent device id + String get deviceId { + var dId = _prefs.getString(KEY_DEVICE_ID); + if (dId == null) { + dId = Uuid().v4(); + deviceId = dId; } - set deviceId(String deviceId) => _prefs.setString(KEY_DEVICE_ID, deviceId); + return dId; + } - clear() async { - var _tos = agreedToTermsOfService; - var _deviceId = deviceId; - await _prefs.clear(); - deviceId = _deviceId; - agreedToTermsOfService = _tos; - } + set deviceId(String deviceId) => _prefs.setString(KEY_DEVICE_ID, deviceId); + + clear() async { + var _tos = agreedToTermsOfService; + var _deviceId = deviceId; + await _prefs.clear(); + deviceId = _deviceId; + agreedToTermsOfService = _tos; + } } diff --git a/lib/data/features/cards/firestore_cards_repository.dart b/lib/data/features/cards/firestore_cards_repository.dart index 6b3c2bf..73caf6e 100644 --- a/lib/data/features/cards/firestore_cards_repository.dart +++ b/lib/data/features/cards/firestore_cards_repository.dart @@ -6,22 +6,27 @@ import 'package:appsagainsthumanity/data/firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; class FirestoreCardsRepository extends CardsRepository { - final FirebaseFirestore _db; - final CardsCache _cache; + FirebaseFirestore? db; + CardsCache? cache; - FirestoreCardsRepository({FirebaseFirestore firestore, CardsCache cache}) - : _db = firestore ?? FirebaseFirestore.instance, - _cache = cache ?? InMemoryCardsCache(); + FirestoreCardsRepository({ + this.db, + this.cache, + }) : super() { + db = FirebaseFirestore.instance; + cache = InMemoryCardsCache(); + } @override Future> getAvailableCardSets() { return currentUserOrThrow((firebaseUser) async { // Check and return cache if valid - final cachedSets = await _cache.getCardSets(); + final cachedSets = await cache!.getCardSets(); if (cachedSets.isNotEmpty) { return cachedSets; } else { - var cardSetsCollection = _db.collection(FirebaseConstants.COLLECTION_CARD_SETS); + var cardSetsCollection = + db!.collection(FirebaseConstants.COLLECTION_CARD_SETS); var snapshots = await cardSetsCollection.get(); print("${snapshots.docs.length} Card sets found"); final cardSets = snapshots.docs.map((e) { @@ -29,7 +34,7 @@ class FirestoreCardsRepository extends CardsRepository { cardSet.id = e.id; return cardSet; }).toList(); - await _cache.setCardSets(cardSets); + await cache!.setCardSets(cardSets); return cardSets; } }); diff --git a/lib/data/features/cards/model/card_set.dart b/lib/data/features/cards/model/card_set.dart index 61489f6..1692485 100644 --- a/lib/data/features/cards/model/card_set.dart +++ b/lib/data/features/cards/model/card_set.dart @@ -6,12 +6,12 @@ part 'card_set.g.dart'; @JsonSerializable() class CardSet extends Equatable { @JsonKey(ignore: true) - String id; + String? id; - final String name; - final int prompts; - final int responses; - final String source; + final String? name; + final int? prompts; + final int? responses; + final String? source; CardSet({ this.id, @@ -21,7 +21,8 @@ class CardSet extends Equatable { this.source, }); - factory CardSet.fromJson(Map json) => _$CardSetFromJson(json); + factory CardSet.fromJson(Map json) => + _$CardSetFromJson(json); Map toJson() => _$CardSetToJson(this); @@ -31,5 +32,11 @@ class CardSet extends Equatable { } @override - List get props => [id, name, prompts, responses, source]; + List get props => [ + id as String, + name as String, + prompts as int, + responses as int, + source as String, + ]; } diff --git a/lib/data/features/cards/model/prompt_card.dart b/lib/data/features/cards/model/prompt_card.dart index 7121200..e94ed1f 100644 --- a/lib/data/features/cards/model/prompt_card.dart +++ b/lib/data/features/cards/model/prompt_card.dart @@ -14,14 +14,15 @@ class PromptCard extends Equatable { final String source; PromptCard({ - @required this.cid, - @required this.text, - @required this.special, - @required this.set, - @required this.source, + required this.cid, + required this.text, + required this.special, + required this.set, + required this.source, }); - factory PromptCard.fromJson(Map json) => _$PromptCardFromJson(json); + factory PromptCard.fromJson(Map json) => + _$PromptCardFromJson(json); Map toJson() => _$PromptCardToJson(this); @@ -34,4 +35,4 @@ class PromptCard extends Equatable { } } -Map promptCardToJson(PromptCard card) => card?.toJson(); +Map promptCardToJson(PromptCard card) => card.toJson(); diff --git a/lib/data/features/cards/model/prompt_special.dart b/lib/data/features/cards/model/prompt_special.dart index 98e982d..8da8e58 100644 --- a/lib/data/features/cards/model/prompt_special.dart +++ b/lib/data/features/cards/model/prompt_special.dart @@ -1,18 +1,15 @@ -import 'package:kt_dart/kt.dart'; +// import 'package:kt_dart/kt.dart'; -enum PromptSpecial { - pick2, - draw2pick3 -} +enum PromptSpecial { pick2, draw2pick3, derp } -@nullable PromptSpecial promptSpecial(String special) { - if (special != null) { - if (special.toUpperCase() == 'PICK 2') { - return PromptSpecial.pick2; - } else if (special.toUpperCase() == 'DRAW 2 PICK 3' || special.toUpperCase() == 'DRAW 2, PICK 3') { - return PromptSpecial.draw2pick3; - } + if (special != "") { + if (special.toUpperCase() == 'PICK 2') { + return PromptSpecial.pick2; + } else if (special.toUpperCase() == 'DRAW 2 PICK 3' || + special.toUpperCase() == 'DRAW 2, PICK 3') { + return PromptSpecial.draw2pick3; } - return null; + } + return PromptSpecial.derp; } diff --git a/lib/data/features/cards/model/response_card.dart b/lib/data/features/cards/model/response_card.dart index 80b5e8c..3f990b2 100644 --- a/lib/data/features/cards/model/response_card.dart +++ b/lib/data/features/cards/model/response_card.dart @@ -13,17 +13,17 @@ class ResponseCard extends Equatable { final String source; ResponseCard({ - @required this.cid, - @required this.text, - @required this.set, - @required this.source, + required this.cid, + required this.text, + required this.set, + required this.source, }); - factory ResponseCard.fromJson(Map json) => _$ResponseCardFromJson(json); + factory ResponseCard.fromJson(Map json) => + _$ResponseCardFromJson(json); Map toJson() => _$ResponseCardToJson(this); @override List get props => [cid, text, set, source]; } - diff --git a/lib/data/features/game/firestore_game_repository.dart b/lib/data/features/game/firestore_game_repository.dart index 56d437b..c8c34cc 100644 --- a/lib/data/features/game/firestore_game_repository.dart +++ b/lib/data/features/game/firestore_game_repository.dart @@ -14,24 +14,27 @@ import 'package:flutter/services.dart'; import 'package:kt_dart/kt.dart'; class FirestoreGameRepository extends GameRepository { - final FirebaseFirestore db; - final UserRepository userRepository; - - FirestoreGameRepository({FirebaseFirestore firestore, UserRepository userRepository}) - : assert(userRepository != null), - userRepository = userRepository, - db = firestore ?? FirebaseFirestore.instance; + FirebaseFirestore? db; + UserRepository userRepository; + + FirestoreGameRepository({ + this.db, + required this.userRepository, + }) : super() { + db = FirebaseFirestore.instance; + userRepository = UserRepository(); + } @override Future createGame( - KtSet cardSets, { - int prizesToWin = Game.PRIZES_TO_WIN, - int playerLimit = Game.PLAYER_LIMIT, + KtSet? cardSets, { + int prizesToWin = PRIZES_TO_WIN, + int playerLimit = PLAYER_LIMIT, bool pick2Enabled = true, bool draw2Pick3Enabled = true, }) { return currentUserOrThrow((firebaseUser) async { - var newGameDoc = db.collection(FirebaseConstants.COLLECTION_GAMES).doc(); + var newGameDoc = db!.collection(FirebaseConstants.COLLECTION_GAMES).doc(); var game = Game( id: newGameDoc.id, gid: generateId(length: FirebaseConstants.MAX_GID_SIZE), @@ -41,7 +44,7 @@ class FirestoreGameRepository extends GameRepository { playerLimit: playerLimit, pick2Enabled: pick2Enabled, draw2Pick3Enabled: draw2Pick3Enabled, - cardSets: cardSets.map((c) => c.id).asList().toSet(), + cardSets: cardSets!.map((c) => c.id!).asList().toSet(), ); await newGameDoc.set(game.toJson()); @@ -61,13 +64,13 @@ class FirestoreGameRepository extends GameRepository { @override Future findGame(String gid) async { - var snapshots = - await db.collection(FirebaseConstants.COLLECTION_GAMES) - .where('gid', isEqualTo: gid.toUpperCase()) - .limit(1) - .get(); + var snapshots = await db! + .collection(FirebaseConstants.COLLECTION_GAMES) + .where('gid', isEqualTo: gid.toUpperCase()) + .limit(1) + .get(); - if (snapshots != null && snapshots.docs.isNotEmpty) { + if (snapshots != {} && snapshots.docs.isNotEmpty) { var document = snapshots.docs.first; return Game.fromDocument(document); } else { @@ -77,8 +80,8 @@ class FirestoreGameRepository extends GameRepository { @override Future getGame(String gameDocumentId, {bool andJoin = false}) async { - var gameDocument = db.collection(FirebaseConstants.COLLECTION_GAMES) - .doc(gameDocumentId); + var gameDocument = + db!.collection(FirebaseConstants.COLLECTION_GAMES).doc(gameDocumentId); try { var snapshot = await gameDocument.get(); @@ -93,7 +96,13 @@ class FirestoreGameRepository extends GameRepository { } } - return null; + // return null; + return Game( + gid: '', + ownerId: '', + state: GameState.waitingRoom, + cardSets: {}, + ); } @override @@ -101,17 +110,18 @@ class FirestoreGameRepository extends GameRepository { return currentUserOrThrow((firebaseUser) async { if (game.state == GameState.completed) { // We should just delete the usergame ourselfs - await db.collection(FirebaseConstants.COLLECTION_USERS) + await db! + .collection(FirebaseConstants.COLLECTION_USERS) .doc(firebaseUser.uid) .collection(FirebaseConstants.COLLECTION_GAMES) .doc(game.id) .delete(); print("Game already completed, so we just deleted the reference"); } else { - final HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('leaveGame'); - HttpsCallableResult response = await callable.call({ - 'game_id': game.id - }); + final HttpsCallable callable = + FirebaseFunctions.instance.httpsCallable('leaveGame'); + HttpsCallableResult response = + await callable.call({'game_id': game.id}); print("Game left! ${response.data}"); } }); @@ -120,42 +130,45 @@ class FirestoreGameRepository extends GameRepository { @override Stream> observeJoinedGames() { return streamCurrentUserOrThrow((firebaseUser) { - return db + return db! .collection(FirebaseConstants.COLLECTION_USERS) .doc(firebaseUser.uid) .collection(FirebaseConstants.COLLECTION_GAMES) .snapshots() - .map((querySnapshot) => querySnapshot.docs.map((e) => UserGame.fromDocument(e)).toList()); + .map((querySnapshot) => + querySnapshot.docs.map((e) => UserGame.fromDocument(e)).toList()); }); } @override Stream observeGame(String gameDocumentId) { - var document = db.collection(FirebaseConstants.COLLECTION_GAMES).doc(gameDocumentId); + var document = + db!.collection(FirebaseConstants.COLLECTION_GAMES).doc(gameDocumentId); return document.snapshots().map((snapshot) => Game.fromDocument(snapshot)); } @override Stream> observePlayers(String gameDocumentId) { - var collection = db + var collection = db! .collection(FirebaseConstants.COLLECTION_GAMES) .doc(gameDocumentId) .collection(FirebaseConstants.COLLECTION_PLAYERS); - return collection.snapshots().map((snapshots) => snapshots.docs.map((e) => Player.fromDocument(e)).toList()); + return collection.snapshots().map((snapshots) => + snapshots.docs.map((e) => Player.fromDocument(e)).toList()); } @override Stream> observeDownvotes(String gameDocumentId) { - var collection = db + var collection = db! .collection(FirebaseConstants.COLLECTION_GAMES) .doc(gameDocumentId) .collection(FirebaseConstants.COLLECTION_DOWNVOTES) .doc(FirebaseConstants.DOCUMENT_TALLY); return collection.snapshots().map((snapshot) { - if (snapshot.data != null) { + if (snapshot.data != {}) { final data = snapshot.data() ?? {}; print(data); print("Contains votes: ${data.containsKey('votes')}"); @@ -169,7 +182,7 @@ class FirestoreGameRepository extends GameRepository { @override Future addRandoCardrissian(String gameDocumentId) async { - var document = db + var document = db! .collection(FirebaseConstants.COLLECTION_GAMES) .doc(gameDocumentId) .collection(FirebaseConstants.COLLECTION_PLAYERS) @@ -178,7 +191,7 @@ class FirestoreGameRepository extends GameRepository { var rando = Player( id: FirebaseConstants.DOCUMENT_RANDO_CARDRISSIAN, name: "Rando Cardrissian", - avatarUrl: null, + avatarUrl: "", isRandoCardrissian: true); await document.set(rando.toJson()); @@ -186,17 +199,22 @@ class FirestoreGameRepository extends GameRepository { @override Future startGame(String gameDocumentId) async { - final HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('startGame'); - dynamic response = await callable.call({'game_id': gameDocumentId}); + final HttpsCallable callable = + FirebaseFunctions.instance.httpsCallable('startGame'); + dynamic response = + await callable.call({'game_id': gameDocumentId}); print("Start game Successful! $response"); } @override - Future submitResponse(String gameDocumentId, List cards) async { - final HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('submitResponses'); + Future submitResponse( + String gameDocumentId, List cards) async { + final HttpsCallable callable = + FirebaseFunctions.instance.httpsCallable('submitResponses'); dynamic response = await callable.call({ 'game_id': gameDocumentId, - 'indexed_responses': cards.asMap().map((key, value) => MapEntry(key.toString(), value.cid)) + 'indexed_responses': + cards.asMap().map((key, value) => MapEntry(key.toString(), value.cid)) }); print("Responses Submitted! $response"); } @@ -204,7 +222,7 @@ class FirestoreGameRepository extends GameRepository { @override Future downVoteCurrentPrompt(String gameDocumentId) { return currentUserOrThrow((firebaseUser) async { - var gameDocument = db + var gameDocument = db! .collection(FirebaseConstants.COLLECTION_GAMES) .doc(gameDocumentId) .collection(FirebaseConstants.COLLECTION_DOWNVOTES) @@ -224,14 +242,15 @@ class FirestoreGameRepository extends GameRepository { } @override - Future waveAtPlayer(String gameDocumentId, String playerId, [String message]) { + Future waveAtPlayer(String gameDocumentId, String playerId, + [String? message]) { return currentUserOrThrow((firebaseUser) async { - final HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('wave'); + final HttpsCallable callable = + FirebaseFunctions.instance.httpsCallable('wave'); dynamic response = await callable.call({ 'game_id': gameDocumentId, 'player_id': playerId, - if (message != null) - 'message': message, + if (message != "") 'message': message, }); print("Wave sent to player successfully! $response"); }); @@ -240,30 +259,31 @@ class FirestoreGameRepository extends GameRepository { @override Future reDealHand(String gameDocumentId) { return currentUserOrThrow((firebaseUser) async { - final HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('reDealHand'); - dynamic response = await callable.call({'game_id': gameDocumentId}); + final HttpsCallable callable = + FirebaseFunctions.instance.httpsCallable('reDealHand'); + dynamic response = + await callable.call({'game_id': gameDocumentId}); print("Hand re-dealt successfully! $response"); }); } @override Future pickWinner(String gameDocumentId, String playerId) async { - final HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('pickWinner'); - dynamic response = await callable.call({'game_id': gameDocumentId, 'player_id': playerId}); + final HttpsCallable callable = + FirebaseFunctions.instance.httpsCallable('pickWinner'); + dynamic response = await callable.call( + {'game_id': gameDocumentId, 'player_id': playerId}); print("Winner picked Successful! $response"); } - Future _addSelfToGame({String gameDocumentId, String gid}) async { + Future _addSelfToGame({String? gameDocumentId, String? gid}) async { var user = await userRepository.getUser(); - final HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('joinGame'); + final HttpsCallable callable = + FirebaseFunctions.instance.httpsCallable('joinGame'); HttpsCallableResult response = await callable.call({ - if (gameDocumentId != null) - 'game_id': gameDocumentId, - - if (gid != null) - 'gid': gid.toUpperCase(), - + if (gameDocumentId != "") 'game_id': gameDocumentId, + if (gid != "") 'gid': gid!.toUpperCase(), 'name': user.name, 'avatar': user.avatarUrl }); @@ -276,11 +296,10 @@ class FirestoreGameRepository extends GameRepository { @override Future kickPlayer(String gameDocumentId, String playerId) async { - final HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('kickPlayer'); - HttpsCallableResult response = await callable.call({ - 'game_id': gameDocumentId, - 'player_id': playerId - }); + final HttpsCallable callable = + FirebaseFunctions.instance.httpsCallable('kickPlayer'); + HttpsCallableResult response = await callable.call( + {'game_id': gameDocumentId, 'player_id': playerId}); print("Player kicked! ${response.data}"); } diff --git a/lib/data/features/game/game_repository.dart b/lib/data/features/game/game_repository.dart index d4a55f7..38a4e23 100644 --- a/lib/data/features/game/game_repository.dart +++ b/lib/data/features/game/game_repository.dart @@ -8,12 +8,11 @@ import 'package:appsagainsthumanity/data/features/users/model/user_game.dart'; import 'package:kt_dart/collection.dart'; abstract class GameRepository { - /// Create a new game with the provided list of card sets Future createGame( KtSet cardSets, { - int prizesToWin = Game.PRIZES_TO_WIN, - int playerLimit = Game.PLAYER_LIMIT, + int prizesToWin = PRIZES_TO_WIN, + int playerLimit = PLAYER_LIMIT, bool pick2Enabled = true, bool draw2Pick3Enabled = true, }); @@ -62,7 +61,8 @@ abstract class GameRepository { /// Wave at a player to re-engage them in the game. Optionally provide a [message] to send to the /// user. - Future waveAtPlayer(String gameDocumentId, String playerId, [String message]); + Future waveAtPlayer(String gameDocumentId, String playerId, + [String message]); /// Re-deal your hand in exchange for one prize card, if you have one Future reDealHand(String gameDocumentId); diff --git a/lib/data/features/game/model/game.dart b/lib/data/features/game/model/game.dart index c4af36e..c852cb9 100644 --- a/lib/data/features/game/model/game.dart +++ b/lib/data/features/game/model/game.dart @@ -2,17 +2,17 @@ import 'package:appsagainsthumanity/data/features/game/model/game_state.dart'; import 'package:appsagainsthumanity/data/features/game/model/turn.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; +// import 'package:meta/meta.dart'; part 'game.g.dart'; +const PRIZES_TO_WIN = 7; +const PLAYER_LIMIT = 30; + @JsonSerializable() class Game { - static const PRIZES_TO_WIN = 7; - static const PLAYER_LIMIT = 30; - @JsonKey(ignore: true) - String id; + String? id; final String gid; final String ownerId; final GameState state; @@ -21,18 +21,18 @@ class Game { final int playerLimit; final bool pick2Enabled; final bool draw2Pick3Enabled; - final List judgeRotation; + final List? judgeRotation; final Set cardSets; @JsonKey(toJson: _turnToJson) - final Turn turn; - final String winner; + final Turn? turn; + final String? winner; Game({ this.id, - @required this.gid, - @required this.ownerId, - @required this.state, - @required this.cardSets, + required this.gid, + required this.ownerId, + required this.state, + required this.cardSets, this.round = 1, this.prizesToWin = PRIZES_TO_WIN, this.playerLimit = PLAYER_LIMIT, @@ -44,7 +44,7 @@ class Game { }); factory Game.fromDocument(DocumentSnapshot snapshot) { - var game = Game.fromJson(snapshot.data()); + var game = Game.fromJson(snapshot.data() as Map); game.id = snapshot.id; return game; } @@ -54,16 +54,16 @@ class Game { Map toJson() => _$GameToJson(this); Game copyWith({ - String id, - String gid, - String ownerId, - GameState state, - int round, - int prizesToWin, - List judgeRotation, - Set cardSets, - Turn turn, - String winner, + String? id, + String? gid, + String? ownerId, + GameState? state, + int? round, + int? prizesToWin, + List? judgeRotation, + Set? cardSets, + Turn? turn, + String? winner, }) { return Game( id: id ?? this.id, @@ -80,4 +80,4 @@ class Game { } } -Map _turnToJson(Turn turn) => turn?.toJson(); +Map _turnToJson(Turn turn) => turn.toJson(); diff --git a/lib/data/features/game/model/player.dart b/lib/data/features/game/model/player.dart index 478d720..b31a0b3 100644 --- a/lib/data/features/game/model/player.dart +++ b/lib/data/features/game/model/player.dart @@ -22,24 +22,24 @@ class Player { final bool isInactive; @JsonKey(includeIfNull: false) - final List hand; + final List? hand; @JsonKey(includeIfNull: false) - final List prizes; - - Player({ - @required this.id, - @required this.name, - this.avatarUrl, - this.hand, - this.prizes, - bool isRandoCardrissian = false, - bool isInactive = false - }) : isRandoCardrissian = isRandoCardrissian, + final List? prizes; + + Player( + {required this.id, + required this.name, + required this.avatarUrl, + this.hand, + this.prizes, + bool isRandoCardrissian = false, + bool isInactive = false}) + : isRandoCardrissian = isRandoCardrissian, isInactive = isInactive; factory Player.fromDocument(DocumentSnapshot documentSnapshot) => - Player.fromJson(documentSnapshot.data()); + Player.fromJson(documentSnapshot.data() as Map); factory Player.fromJson(Map json) => _$PlayerFromJson(json); diff --git a/lib/data/features/game/model/turn.dart b/lib/data/features/game/model/turn.dart index 0624a17..da1eec9 100644 --- a/lib/data/features/game/model/turn.dart +++ b/lib/data/features/game/model/turn.dart @@ -17,13 +17,13 @@ class Turn { @JsonKey(toJson: responsesToJson) final Map> responses; - @JsonKey(nullable: true, toJson: turnWinnerToJson) - final TurnWinner winner; + @JsonKey(toJson: turnWinnerToJson) + final TurnWinner? winner; Turn({ - @required this.judgeId, - @required this.promptCard, - @required this.responses, + required this.judgeId, + required this.promptCard, + required this.responses, this.winner, }); @@ -32,21 +32,24 @@ class Turn { Map toJson() => _$TurnToJson(this); Turn copyWith({ - String judgeId, - PromptCard promptCard, - Map> responses, - TurnWinner winner, + String? judgeId, + PromptCard? promptCard, + Map>? responses, + TurnWinner? winner, }) { return Turn( - judgeId: judgeId ?? this.judgeId, - promptCard: promptCard ?? this.promptCard, - responses: responses ?? this.responses, - winner: winner ?? this.winner); + judgeId: judgeId ?? this.judgeId, + promptCard: promptCard ?? this.promptCard, + responses: responses ?? this.responses, + winner: winner ?? this.winner, + ); } } Map _promptCardToJson(PromptCard card) => card.toJson(); -Map responsesToJson(Map> responses) { - return responses?.map((k, e) => MapEntry(k, e.map((e) => e.toJson()).toList())); +Map responsesToJson( + Map> responses) { + return responses + .map((k, e) => MapEntry(k, e.map((e) => e.toJson()).toList())); } diff --git a/lib/data/features/game/model/turn_winner.dart b/lib/data/features/game/model/turn_winner.dart index 31f8f25..44e24ba 100644 --- a/lib/data/features/game/model/turn_winner.dart +++ b/lib/data/features/game/model/turn_winner.dart @@ -9,18 +9,18 @@ part 'turn_winner.g.dart'; @immutable @JsonSerializable() class TurnWinner extends Equatable { - final String playerId; - final String playerName; - final String playerAvatarUrl; - final bool isRandoCardrissian; + final String? playerId; + final String? playerName; + final String? playerAvatarUrl; + final bool? isRandoCardrissian; @JsonKey(toJson: promptCardToJson) - final PromptCard promptCard; + final PromptCard? promptCard; @JsonKey(toJson: responsesToJson) - final List response; + final List? response; - @JsonKey(toJson: playerResponsesToJson, nullable: true) - final Map> responses; + @JsonKey(toJson: playerResponsesToJson) + final Map>? responses; TurnWinner({ this.playerId, @@ -29,10 +29,11 @@ class TurnWinner extends Equatable { this.isRandoCardrissian, this.promptCard, this.response, - this.responses + this.responses, }); - factory TurnWinner.fromJson(Map json) => _$TurnWinnerFromJson(json); + factory TurnWinner.fromJson(Map json) => + _$TurnWinnerFromJson(json); Map toJson() => _$TurnWinnerToJson(this); @@ -43,13 +44,25 @@ class TurnWinner extends Equatable { } @override - List get props => [playerId, playerName, playerAvatarUrl, isRandoCardrissian, promptCard, response, responses]; + List get props => [ + playerId as String, + playerName as String, + playerAvatarUrl as String, + isRandoCardrissian as bool, + promptCard as PromptCard, + response as List, + responses as Map>, + ]; } -Map turnWinnerToJson(TurnWinner turnWinner) => turnWinner?.toJson(); +Map turnWinnerToJson(TurnWinner turnWinner) => + turnWinner.toJson(); -List> responsesToJson(List cards) => cards?.map((e) => e.toJson())?.toList(); +List> responsesToJson(List cards) => + cards.map((e) => e.toJson()).toList(); -Map playerResponsesToJson(Map> responses) { - return responses?.map((k, e) => MapEntry(k, e.map((e) => e.toJson()).toList())); +Map playerResponsesToJson( + Map> responses) { + return responses + .map((k, e) => MapEntry(k, e.map((e) => e.toJson()).toList())); } diff --git a/lib/data/features/users/model/user.dart b/lib/data/features/users/model/user.dart index d3534b5..229402b 100644 --- a/lib/data/features/users/model/user.dart +++ b/lib/data/features/users/model/user.dart @@ -12,10 +12,10 @@ class User { final DateTime updatedAt; User({ - @required this.id, - @required this.name, - @required this.avatarUrl, - @required this.updatedAt, + required this.id, + required this.name, + required this.avatarUrl, + required this.updatedAt, }); factory User.fromJson(Map json) => _$UserFromJson(json); diff --git a/lib/data/features/users/model/user_game.dart b/lib/data/features/users/model/user_game.dart index 5461ef8..3c74f07 100644 --- a/lib/data/features/users/model/user_game.dart +++ b/lib/data/features/users/model/user_game.dart @@ -1,32 +1,33 @@ import 'package:appsagainsthumanity/data/features/game/model/game_state.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; +// import 'package:meta/meta.dart'; part 'user_game.g.dart'; @JsonSerializable() class UserGame { @JsonKey(ignore: true) - String id; + String? id; final String gid; final GameState state; final DateTime joinedAt; UserGame({ this.id, - @required this.gid, - @required this.state, - @required this.joinedAt, + required this.gid, + required this.state, + required this.joinedAt, }); factory UserGame.fromDocument(DocumentSnapshot document) { - var game = UserGame.fromJson(document.data()); + var game = UserGame.fromJson(document.data() as Map); game.id = document.id; return game; } - factory UserGame.fromJson(Map json) => _$UserGameFromJson(json); + factory UserGame.fromJson(Map json) => + _$UserGameFromJson(json); Map toJson() => _$UserGameToJson(this); } diff --git a/lib/data/features/users/user_repository.dart b/lib/data/features/users/user_repository.dart index cc5441e..e9a0d2c 100644 --- a/lib/data/features/users/user_repository.dart +++ b/lib/data/features/users/user_repository.dart @@ -1,4 +1,4 @@ -import 'dart:io'; +// import 'dart:io'; import 'dart:typed_data'; import 'package:appsagainsthumanity/data/app_preferences.dart'; @@ -7,7 +7,7 @@ import 'package:appsagainsthumanity/data/features/users/model/user.dart'; import 'package:appsagainsthumanity/data/firestore.dart'; import 'package:appsagainsthumanity/internal/push.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:cloud_functions/cloud_functions.dart'; +// import 'package:cloud_functions/cloud_functions.dart'; import 'package:firebase_auth/firebase_auth.dart' as fb; import 'package:firebase_storage/firebase_storage.dart'; import 'package:google_sign_in/google_sign_in.dart'; @@ -52,10 +52,8 @@ class UserRepository { Future updateDisplayName(String name) { return currentUserOrThrow((firebaseUser) async { // Be sure to update our Auth Obj - await firebaseUser.updateProfile( - displayName: name, - photoURL: firebaseUser.photoURL, - ); + await firebaseUser.updateDisplayName(name); + await firebaseUser.updatePhotoURL(firebaseUser.photoURL); await _userDocument(firebaseUser).update({ 'name': name, @@ -70,10 +68,8 @@ class UserRepository { await ref.delete(); // Be sure to update our Auth Obj - await firebaseUser.updateProfile( - displayName: firebaseUser.displayName, - photoURL: null, - ); + await firebaseUser.updateDisplayName(firebaseUser.displayName); + await firebaseUser.updatePhotoURL(""); await _userDocument(firebaseUser).update({ 'avatarUrl': null, @@ -89,10 +85,8 @@ class UserRepository { var downloadUrl = await refSnapshot.ref.getDownloadURL(); // Be sure to update our Auth Obj - await firebaseUser.updateProfile( - displayName: firebaseUser.displayName, - photoURL: downloadUrl, - ); + await firebaseUser.updateDisplayName(firebaseUser.displayName); + await firebaseUser.updatePhotoURL(downloadUrl); await _userDocument(firebaseUser).update({ 'avatarUrl': downloadUrl.toString(), @@ -111,12 +105,12 @@ class UserRepository { try { var acct = await _googleSignIn.signIn(); - var auth = await acct.authentication; + var auth = await acct?.authentication; var result = await _auth.signInWithCredential( fb.GoogleAuthProvider.credential( - idToken: auth.idToken, - accessToken: auth.accessToken, + idToken: auth?.idToken, + accessToken: auth?.accessToken, ), ); @@ -141,24 +135,28 @@ class UserRepository { ); var name = Player.DEFAULT_NAME; - if (result.givenName != null) { + if (result.givenName != "") { print( "Apple Name(given=${result.givenName}, family=${result.familyName})"); - name = result.givenName; + name = result.givenName!; } - final fb.UserCredential _result = await _auth.signInWithCredential(credential); + final fb.UserCredential _result = + await _auth.signInWithCredential(credential); return _finishSigningInWithResult(_result, name: name); } Future signInWithEmail(String email, String password) async { - final fb.UserCredential _result = await _auth.signInWithEmailAndPassword(email: email, password: password); + final fb.UserCredential _result = await _auth.signInWithEmailAndPassword( + email: email, password: password); return _finishSigningInWithResult(_result); } - Future signUpWithEmail(String email, String password, [String username]) async { - final fb.UserCredential _result = await _auth.createUserWithEmailAndPassword(email: email, password: password); - return _finishSigningInWithResult(_result, name: username); + Future signUpWithEmail(String email, String password, + [String? username]) async { + final fb.UserCredential _result = await _auth + .createUserWithEmailAndPassword(email: email, password: password); + return _finishSigningInWithResult(_result, name: username!); } Future signInAnonymously() async { @@ -167,38 +165,37 @@ class UserRepository { } Future _finishSigningInWithResult(fb.UserCredential result, - {String name}) async { + {String? name}) async { try { if (result.user != null) { - if (name != null && result.user.displayName != name) { - await result.user.updateProfile( - displayName: name, - photoURL: result.user.photoURL, - ); - await result.user.reload(); - print("Updated user's name to ${result.user.displayName}"); + if (name != null && result.user?.displayName != name) { + await result.user?.updateDisplayName(name); + await result.user?.updatePhotoURL(result.user?.photoURL); + await result.user?.reload(); + print("Updated user's name to ${result.user?.displayName}"); } // Create the user obj acct in FB - var userDoc = _userDocument(result.user); + var userDoc = _userDocument(result.user!); await userDoc.set({ - "name": result.user.displayName, - "avatarUrl": result.user.photoURL, + "name": result.user?.displayName, + "avatarUrl": result.user?.photoURL, "updatedAt": Timestamp.now(), }, SetOptions(merge: true)); Logger("UserRepository").fine( - "Signed-in! User(id=${result.user.uid}, name=${result.user.displayName}, photoUrl=${result.user.photoURL})"); + "Signed-in! User(id=${result.user?.uid}, name=${result.user?.displayName}, photoUrl=${result.user?.photoURL})"); // Excellent! Let's create our device await PushNotifications().checkAndUpdateToken(force: true); return User( - id: result.user.uid, - name: result.user.displayName, - avatarUrl: result.user.photoURL, - updatedAt: DateTime.now()); + id: result.user!.uid, + name: result.user!.displayName!, + avatarUrl: result.user!.photoURL!, + updatedAt: DateTime.now(), + ); } else { throw result; } @@ -217,14 +214,12 @@ class UserRepository { Future deleteAccount() async { await AppPreferences().clear(); var user = _auth.currentUser; - await user.delete(); - await _profilePhotoReference(user).delete(); + await user?.delete(); + await _profilePhotoReference(user!).delete(); } DocumentReference _userDocument(fb.User user) { - return _db - .collection(FirebaseConstants.COLLECTION_USERS) - .doc(user.uid); + return _db.collection(FirebaseConstants.COLLECTION_USERS).doc(user.uid); } Reference _profilePhotoReference(fb.User user) { diff --git a/lib/data/firestore.dart b/lib/data/firestore.dart index 89c7249..0e4fa0d 100644 --- a/lib/data/firestore.dart +++ b/lib/data/firestore.dart @@ -1,37 +1,37 @@ import 'package:firebase_auth/firebase_auth.dart'; class FirebaseConstants { - FirebaseConstants._(); + FirebaseConstants._(); - static const COLLECTION_USERS = "users"; - static const COLLECTION_DEVICES = "devices"; - static const COLLECTION_CARD_SETS = "cardSets"; - static const COLLECTION_GAMES = "games"; - static const COLLECTION_PLAYERS = "players"; - static const COLLECTION_DOWNVOTES = "downvotes"; + static const COLLECTION_USERS = "users"; + static const COLLECTION_DEVICES = "devices"; + static const COLLECTION_CARD_SETS = "cardSets"; + static const COLLECTION_GAMES = "games"; + static const COLLECTION_PLAYERS = "players"; + static const COLLECTION_DOWNVOTES = "downvotes"; - static const DOCUMENT_RANDO_CARDRISSIAN = "rando-cardrissian"; - static const DOCUMENT_TALLY = "tally"; + static const DOCUMENT_RANDO_CARDRISSIAN = "rando-cardrissian"; + static const DOCUMENT_TALLY = "tally"; - static const MAX_GID_SIZE = 5; + static const MAX_GID_SIZE = 5; } -class UserNotFoundException { } +class UserNotFoundException {} Future currentUserOrThrow(Future Function(User user) action) async { - final User currentUser = FirebaseAuth.instance.currentUser; - if (currentUser != null) { - return action(currentUser); - } - throw UserNotFoundException(); + final User currentUser = FirebaseAuth.instance.currentUser!; + if (currentUser != {}) { + return action(currentUser); + } + throw UserNotFoundException(); } -Stream streamCurrentUserOrThrow(Stream Function(User user) action) async* { - final User currentUser = FirebaseAuth.instance.currentUser; - if (currentUser != null) { - yield* action(currentUser); - } else { - throw UserNotFoundException(); - } +Stream streamCurrentUserOrThrow( + Stream Function(User user) action) async* { + final User currentUser = FirebaseAuth.instance.currentUser!; + if (currentUser != {}) { + yield* action(currentUser); + } else { + throw UserNotFoundException(); + } } - diff --git a/lib/internal/analytics.dart b/lib/internal/analytics.dart index cf63947..9e98a78 100644 --- a/lib/internal/analytics.dart +++ b/lib/internal/analytics.dart @@ -1,6 +1,6 @@ import 'package:firebase_analytics/firebase_analytics.dart'; -class Analytics with FirebaseAnalytics { +class Analytics extends FirebaseAnalytics { static Analytics _instance = Analytics._(); FirebaseAnalytics _firebaseAnalytics; @@ -14,38 +14,41 @@ class Analytics with FirebaseAnalytics { } @override - Future setCurrentScreen({String screenName, String screenClassOverride = 'Flutter'}) { - return _firebaseAnalytics?.setCurrentScreen(screenName: screenName, screenClassOverride: 'Flutter') - ?? Future.value(); + Future setCurrentScreen( + {String? screenName, String screenClassOverride = 'Flutter'}) { + return _firebaseAnalytics?.setCurrentScreen( + screenName: screenName, screenClassOverride: 'Flutter') ?? + Future.value(); } @override - Future logEvent({String name, Map parameters}) { - return _firebaseAnalytics?.logEvent(name: name, parameters: parameters) - ?? Future.value(); + Future logEvent({String? name, Map? parameters}) { + return _firebaseAnalytics?.logEvent(name: name!, parameters: parameters) ?? + Future.value(); } @override - Future logSelectContent({String contentType, String itemId}) { - return _firebaseAnalytics?.logSelectContent(contentType: contentType, itemId: itemId) - ?? Future.value(); + Future logSelectContent({String? contentType, String? itemId}) { + return _firebaseAnalytics?.logSelectContent( + contentType: contentType!, itemId: itemId!) ?? + Future.value(); } @override - Future logViewItemList({String itemCategory}) { - return _firebaseAnalytics?.logViewItemList(itemCategory: itemCategory) - ?? Future.value(); + Future logViewItemList({String? itemCategory}) { + return _firebaseAnalytics?.logViewItemList(itemCategory: itemCategory) ?? + Future.value(); } @override - Future logLogin({String loginMethod}) { - return _firebaseAnalytics?.logLogin(loginMethod: loginMethod) - ?? Future.value(); + Future logLogin({String? loginMethod}) { + return _firebaseAnalytics?.logLogin(loginMethod: loginMethod) ?? + Future.value(); } @override - Future logSignUp({String signUpMethod}) { - return _firebaseAnalytics?.logSignUp(signUpMethod: signUpMethod) - ?? Future.value(); + Future logSignUp({String? signUpMethod}) { + return _firebaseAnalytics?.logSignUp(signUpMethod: signUpMethod!) ?? + Future.value(); } } diff --git a/lib/internal/dynamic_links.dart b/lib/internal/dynamic_links.dart index bb9fded..7b1be76 100644 --- a/lib/internal/dynamic_links.dart +++ b/lib/internal/dynamic_links.dart @@ -3,63 +3,64 @@ import 'package:firebase_dynamic_links/firebase_dynamic_links.dart'; import 'package:flutter/material.dart'; class DynamicLinks { - DynamicLinks._(); + DynamicLinks._(); - static Future createLink(String gameDocumentId) async { - if (!kIsWeb) { - final DynamicLinkParameters params = DynamicLinkParameters( - uriPrefix: "https://appsagainsthumanity.com/games", - link: Uri.parse("https://appsagainsthumanity.com/games/$gameDocumentId"), - androidParameters: AndroidParameters( - packageName: 'com.ftinc.appsagainsthumanity', - ), - iosParameters: IosParameters( - appStoreId: "1509268296", - bundleId: "com.ftinc.appsagainsthumanity", - ), - socialMetaTagParameters: SocialMetaTagParameters( - title: "Come join my game!", - description: "Play a game of Apps Against Humanity", - ), - ); + static Future createLink(String gameDocumentId) async { + if (!kIsWeb) { + final DynamicLinkParameters params = DynamicLinkParameters( + uriPrefix: "https://appsagainsthumanity.com/games", + link: + Uri.parse("https://appsagainsthumanity.com/games/$gameDocumentId"), + androidParameters: AndroidParameters( + packageName: 'com.ftinc.appsagainsthumanity', + ), + iosParameters: IOSParameters( + appStoreId: "1509268296", + bundleId: "com.ftinc.appsagainsthumanity", + ), + socialMetaTagParameters: SocialMetaTagParameters( + title: "Come join my game!", + description: "Play a game of Apps Against Humanity", + ), + ); - var shortLink = await params.buildShortLink(); - return shortLink.shortUrl; - } else { - return Uri.parse("https://appsagainsthumanity.com/games/$gameDocumentId"); - } + var shortLink = + await FirebaseDynamicLinks.instance.buildShortLink(params); + return Uri.parse(shortLink.toString()); + } else { + return Uri.parse("https://appsagainsthumanity.com/games/$gameDocumentId"); } + } - static void initDynamicLinks(BuildContext context, void Function(String gameId) callback) async { - if (!kIsWeb) { - final PendingDynamicLinkData data = await FirebaseDynamicLinks.instance.getInitialLink(); - final Uri deepLink = data?.link; + static void initDynamicLinks( + BuildContext context, void Function(String gameId) callback) async { + if (!kIsWeb) { + final PendingDynamicLinkData? data = + await FirebaseDynamicLinks.instance.getInitialLink(); + final Uri deepLink = data!.link; - if (deepLink != null) { - var lastSegment = deepLink.pathSegments.last; - if (lastSegment != null) { - print("Joining $lastSegment from dynamic link"); - callback(lastSegment); - } - } + if (deepLink != "") { + var lastSegment = deepLink.pathSegments.last; + if (lastSegment != "") { + print("Joining $lastSegment from dynamic link"); + callback(lastSegment); + } + } - FirebaseDynamicLinks.instance.onLink( - onSuccess: (PendingDynamicLinkData dynamicLink) async { - final Uri deepLink = dynamicLink?.link; + FirebaseDynamicLinks.instance.onLink.listen((dynamicLink) async { + final Uri deepLink = dynamicLink.link; - if (deepLink != null) { - var lastSegment = deepLink.pathSegments.last; - if (lastSegment != null) { - print("Joining $lastSegment from dynamic link"); - callback(lastSegment); - } - } - }, - onError: (OnLinkErrorException e) async { - print('onLinkError'); - print(e.message); - } - ); + if (deepLink != "") { + var lastSegment = deepLink.pathSegments.last; + if (lastSegment != "") { + print("Joining $lastSegment from dynamic link"); + callback(lastSegment); + } } + }).onError((error) async { + print('onLinkError'); + print(error.message); + }); } + } } diff --git a/lib/internal/localization.dart b/lib/internal/localization.dart index d79c425..6e6cdf9 100644 --- a/lib/internal/localization.dart +++ b/lib/internal/localization.dart @@ -7,7 +7,8 @@ class AppLocalization { static Future load(Locale locale) { currentLocale = locale; - final String name = locale.countryCode == null ? locale.languageCode : locale.toString(); + final String name = + locale.countryCode == null ? locale.languageCode : locale.toString(); final String localeName = Intl.canonicalizedLocale(name); return initializeMessages(localeName).then((bool _) { @@ -16,30 +17,48 @@ class AppLocalization { }); } - static AppLocalization of(BuildContext context) { + static AppLocalization? of(BuildContext context) { return Localizations.of(context, AppLocalization); } String get appName => Intl.message('AppsAgainstHumanity', name: 'appName'); - String get appNameDisplay => Intl.message('Apps Against\nHumanity', name: 'appNameDisplay'); - String get actionSignIn => Intl.message('Sign in with Google', name: 'actionSignIn'); - String get actionSignInAnonymously => Intl.message('Sign in anonymously', name: 'actionSignInAnonymously'); - String get actionSignInEmail => Intl.message('Sign in with Email', name: 'actionSignInEmail'); - String get actionEmailSignUp => Intl.message('Sign up', name: 'actionEmailSignUp'); - String get actionEmailSignIn => Intl.message('Sign in', name: 'actionEmailSignIn'); - String get actionEmailAltSignUp => Intl.message('or sign up', name: 'actionEmailAltSignUp'); - String get actionEmailAltSignIn => Intl.message('or sign in', name: 'actionEmailAltSignIn'); + String get appNameDisplay => + Intl.message('Apps Against\nHumanity', name: 'appNameDisplay'); + String get actionSignIn => + Intl.message('Sign in with Google', name: 'actionSignIn'); + String get actionSignInAnonymously => + Intl.message('Sign in anonymously', name: 'actionSignInAnonymously'); + String get actionSignInEmail => + Intl.message('Sign in with Email', name: 'actionSignInEmail'); + String get actionEmailSignUp => + Intl.message('Sign up', name: 'actionEmailSignUp'); + String get actionEmailSignIn => + Intl.message('Sign in', name: 'actionEmailSignIn'); + String get actionEmailAltSignUp => + Intl.message('or sign up', name: 'actionEmailAltSignUp'); + String get actionEmailAltSignIn => + Intl.message('or sign in', name: 'actionEmailAltSignIn'); String get hintEmail => Intl.message('Email', name: 'hintEmail'); String get hintPassword => Intl.message('Password', name: 'hintPassword'); - String get hintConfirmPassword => Intl.message('Confirm password', name: 'hintConfirmPassword'); + String get hintConfirmPassword => + Intl.message('Confirm password', name: 'hintConfirmPassword'); String get hintUserName => Intl.message('Username', name: 'hintUserName'); - String get errorInvalidUserName => Intl.message('Please enter a valid username', name: 'errorInvalidUserName'); - String get errorInvalidEmailAddress => Intl.message('Please enter a valid email address', name: 'errorInvalidEmailAddress'); - String get errorInvalidPasswordLength => Intl.message('You must enter a password of at lease 8 characters', name: 'errorInvalidPasswordLength'); - String get errorMismatchingPasswords => Intl.message('This must match the above password', name: 'errorMismatchingPasswords'); + String get errorInvalidUserName => + Intl.message('Please enter a valid username', + name: 'errorInvalidUserName'); + String get errorInvalidEmailAddress => + Intl.message('Please enter a valid email address', + name: 'errorInvalidEmailAddress'); + String get errorInvalidPasswordLength => + Intl.message('You must enter a password of at lease 8 characters', + name: 'errorInvalidPasswordLength'); + String get errorMismatchingPasswords => + Intl.message('This must match the above password', + name: 'errorMismatchingPasswords'); String get titleNewGame => Intl.message('New game', name: 'titleNewGame'); - String get actionStartGame => Intl.message('Start game', name: 'actionStartGame'); + String get actionStartGame => + Intl.message('Start game', name: 'actionStartGame'); } class AppLocalizationsDelegate extends LocalizationsDelegate { @@ -62,6 +81,5 @@ class AppLocalizationsDelegate extends LocalizationsDelegate { } extension AppLocalizationExt on BuildContext { - - AppLocalization get strings => AppLocalization.of(this); + AppLocalization get strings => AppLocalization.of(this)!; } diff --git a/lib/internal/logging_bloc_delegate.dart b/lib/internal/logging_bloc_delegate.dart index f7e7d3e..e017529 100644 --- a/lib/internal/logging_bloc_delegate.dart +++ b/lib/internal/logging_bloc_delegate.dart @@ -1,21 +1,21 @@ import 'package:bloc/bloc.dart'; -class LoggingBlocDelegate extends BlocDelegate { - @override - void onEvent(Bloc bloc, Object event) { - super.onEvent(bloc, event); - print(event); - } +class LoggingBlocDelegate extends BlocObserver { + @override + void onEvent(Bloc bloc, Object? event) { + super.onEvent(bloc, event); + print(event); + } - @override - void onError(Bloc bloc, Object error, StackTrace stacktrace) { - super.onError(bloc, error, stacktrace); - print(error); - } + @override + void onError(BlocBase bloc, Object error, StackTrace stacktrace) { + super.onError(bloc, error, stacktrace); + print(error); + } - @override - void onTransition(Bloc bloc, Transition transition) { - super.onTransition(bloc, transition); - print(transition); - } + @override + void onTransition(Bloc bloc, Transition transition) { + super.onTransition(bloc, transition); + print(transition); + } } diff --git a/lib/internal/push.dart b/lib/internal/push.dart index 19aff00..cd8565e 100644 --- a/lib/internal/push.dart +++ b/lib/internal/push.dart @@ -4,13 +4,14 @@ import 'package:appsagainsthumanity/data/app_preferences.dart'; import 'package:appsagainsthumanity/data/features/devices/device_repository.dart'; import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/data/features/game/model/turn.dart'; +import 'package:appsagainsthumanity/data/features/game/model/turn_winner.dart'; import 'package:appsagainsthumanity/internal/dynamic_links.dart'; import 'package:appsagainsthumanity/ui/routes.dart'; -import 'package:appsagainsthumanity/internal.dart'; +// import 'package:appsagainsthumanity/internal.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter/cupertino.dart'; +// import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +// import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:logging/logging.dart'; @@ -43,10 +44,11 @@ class PushNotifications { } Future checkAndUpdateToken({bool force = false}) async { - String token = await _firebaseMessaging.getToken(); + String? token = await _firebaseMessaging.getToken(); if (token != AppPreferences().pushToken || force) { - Logger.root.fine("FCM Token is different from what is stored in preferences, updating device..."); - DeviceRepository().updatePushToken(token); + Logger.root.fine( + "FCM Token is different from what is stored in preferences, updating device..."); + DeviceRepository().updatePushToken(token!); } } } @@ -54,19 +56,19 @@ class PushNotifications { class PushNavigator extends StatefulWidget { final Widget child; - PushNavigator({@required this.child}); + PushNavigator({required this.child}); @override _PushNavigatorState createState() => _PushNavigatorState(); } class _PushNavigatorState extends State { - GameRepository _gameRepository; + late GameRepository _gameRepository; @override void initState() { super.initState(); - _gameRepository = context.repository(); + _gameRepository = context.read(); FirebaseMessaging.onMessageOpenedApp.listen((message) { print("onLaunch()"); print(JsonEncoder().convert(message)); @@ -74,7 +76,8 @@ class _PushNavigatorState extends State { }); /// let's go ahead and sign up here to handle dynamic links - DynamicLinks.initDynamicLinks(context, (gameId) => navigateToGame(gameId, andJoin: true)); + DynamicLinks.initDynamicLinks( + context, (gameId) => navigateToGame(gameId, andJoin: true)); } @override @@ -90,43 +93,45 @@ class _PushNavigatorState extends State { } void navigateToGame(String gameId, {bool andJoin = false}) async { - if (gameId != null && gameId.isNotEmpty) { + if (gameId != "" && gameId.isNotEmpty) { var currentRoute = Routes.routeTracer.currentRoute; print(currentRoute); - if (currentRoute.settings.arguments == gameId) { + if (currentRoute?.settings.arguments == gameId) { // It looks like the current game is up fromt print("Game is already in the foreground"); return; } try { var game = await _gameRepository.getGame(gameId, andJoin: andJoin); - if (game != null) { + if (game != {}) { /* * Neuter the turn winner out of the otherwise the winner bottom sheet will never show */ - if (game.turn != null) { + if (game.turn != {}) { game = game.copyWith( - turn: Turn( - judgeId: game.turn.judgeId, - responses: game.turn.responses, - promptCard: game.turn.promptCard, - winner: null)); + turn: Turn( + judgeId: game.turn!.judgeId, + responses: game.turn!.responses, + promptCard: game.turn!.promptCard, + winner: TurnWinner(), + ), + ); } - if (currentRoute.settings.name == Routes.game) { + if (currentRoute?.settings.name == Routes.game) { print("Replacing current shown game"); - currentRoute.navigator.pushReplacement(GamePageRoute(game)); - } else if (currentRoute.settings.name == "/") { + currentRoute?.navigator?.pushReplacement(GamePageRoute(game)); + } else if (currentRoute?.settings.name == "/") { print("User should be in the homescreen, push game"); - currentRoute.navigator.push(GamePageRoute(game)); + currentRoute?.navigator?.push(GamePageRoute(game)); } else if (currentRoute is MaterialPageRoute) { print("User is not in a game or homescreen"); - currentRoute.navigator + currentRoute.navigator! ..popUntil((route) => route.settings.name == "/") ..push(GamePageRoute(game)); } } else { - print("Unable to join the Game($gameId)"); + print("Unable to join the Game($gameId)"); } } catch (e) { print(e); diff --git a/lib/internal/theme.dart b/lib/internal/theme.dart index aef2271..9188eb1 100644 --- a/lib/internal/theme.dart +++ b/lib/internal/theme.dart @@ -2,170 +2,175 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; class AppColors { - AppColors._(); + AppColors._(); - static const primary = Color(0xFFAB47BC); - static const primaryDark = Color(0xFF790e8b); - static const primaryVariant = Color(0xFFdf78ef); - static const colorOnPrimary = Color(0xFFFFFFFF); - static const colorOnPrimaryVariant = Color(0xDD000000); + static const primary = Color(0xFFAB47BC); + static const primaryDark = Color(0xFF790e8b); + static const primaryVariant = Color(0xFFdf78ef); + static const colorOnPrimary = Color(0xFFFFFFFF); + static const colorOnPrimaryVariant = Color(0xDD000000); - static const secondary = Color(0xFF00BCD4); - static const secondaryLight = Color(0xFF80DEEA); - static const secondaryDark = Color(0xFF006064); + static const secondary = Color(0xFF00BCD4); + static const secondaryLight = Color(0xFF80DEEA); + static const secondaryDark = Color(0xFF006064); - static const surface = Color(0xFF3C3C3C); - static const surfaceLight = Color(0xFF4F4F4F); - static const surfaceDark = Color(0xFF303030); - static const responseCardBackground = Color(0xFFF2F2F2); + static const surface = Color(0xFF3C3C3C); + static const surfaceLight = Color(0xFF4F4F4F); + static const surfaceDark = Color(0xFF303030); + static const responseCardBackground = Color(0xFFF2F2F2); - static const error = Color(0xFFFF5252); + static const error = Color(0xFFFF5252); - static const addPhotoBackground = Color(0xFF666666); - static const addPhotoForeground = Colors.white70; + static const addPhotoBackground = Color(0xFF666666); + static const addPhotoForeground = Colors.white70; } class AppThemes { - AppThemes._(); - - static ThemeData get light { - final textTheme = Typography.material2018(platform: defaultTargetPlatform).white; - return ThemeData( - brightness: Brightness.light, - primaryColor: AppColors.primary, - primaryColorDark: AppColors.primaryDark, - primaryColorLight: AppColors.primaryVariant, - accentColor: AppColors.primary, - canvasColor: AppColors.surface, - cardColor: Colors.white, - textTheme: textTheme, - iconTheme: const IconThemeData(color: Colors.white), - disabledColor: Colors.white38, - unselectedWidgetColor: Colors.white70, - dialogBackgroundColor: Colors.grey[700], - appBarTheme: AppBarTheme( - color: AppColors.surfaceDark - ), - snackBarTheme: SnackBarThemeData( - backgroundColor: AppColors.surfaceLight, - contentTextStyle: textTheme.subtitle1, - actionTextColor: AppColors.primary, - disabledActionTextColor: Colors.white30, - ), - bottomAppBarColor: AppColors.surfaceDark, - ); - } - - static ThemeData get dark => - ThemeData( - brightness: Brightness.dark, - primaryColor: AppColors.primaryVariant, - primaryColorDark: AppColors.primaryDark, - primaryColorLight: AppColors.primaryVariant, - floatingActionButtonTheme: FloatingActionButtonThemeData( - foregroundColor: Colors.white - ), - accentColor: AppColors.primaryVariant, - cardColor: Colors.grey[700], - appBarTheme: AppBarTheme( - color: AppColors.surface - ), - snackBarTheme: SnackBarThemeData( - backgroundColor: AppColors.surfaceDark, - contentTextStyle: Typography.material2018(platform: defaultTargetPlatform).white.subtitle1, - actionTextColor: AppColors.primary, - disabledActionTextColor: Colors.white30, - ), - bottomAppBarColor: AppColors.surface, - ); + AppThemes._(); + + static ThemeData get light { + final textTheme = + Typography.material2018(platform: defaultTargetPlatform).white; + final ThemeData theme = ThemeData( + brightness: Brightness.light, + primaryColor: AppColors.primary, + primaryColorDark: AppColors.primaryDark, + primaryColorLight: AppColors.primaryVariant, + // accentColor: AppColors.primary, + canvasColor: AppColors.surface, + cardColor: Colors.white, + textTheme: textTheme, + iconTheme: const IconThemeData(color: Colors.white), + disabledColor: Colors.white38, + unselectedWidgetColor: Colors.white70, + dialogBackgroundColor: Colors.grey[700], + appBarTheme: AppBarTheme(color: AppColors.surfaceDark), + snackBarTheme: SnackBarThemeData( + backgroundColor: AppColors.surfaceLight, + contentTextStyle: textTheme.subtitle1, + actionTextColor: AppColors.primary, + disabledActionTextColor: Colors.white30, + ), + bottomAppBarColor: AppColors.surfaceDark, + ); + return theme.copyWith( + colorScheme: ColorScheme.fromSwatch().copyWith( + secondary: AppColors.primary, + ), + ); + } + + static ThemeData get dark { + final ThemeData theme = ThemeData( + brightness: Brightness.dark, + primaryColor: AppColors.primaryVariant, + primaryColorDark: AppColors.primaryDark, + primaryColorLight: AppColors.primaryVariant, + floatingActionButtonTheme: + FloatingActionButtonThemeData(foregroundColor: Colors.white), + // accentColor: AppColors.primaryVariant, + cardColor: Colors.grey[700], + appBarTheme: AppBarTheme(color: AppColors.surface), + snackBarTheme: SnackBarThemeData( + backgroundColor: AppColors.surfaceDark, + contentTextStyle: + Typography.material2018(platform: defaultTargetPlatform) + .white + .subtitle1, + actionTextColor: AppColors.primary, + disabledActionTextColor: Colors.white30, + ), + bottomAppBarColor: AppColors.surface, + ); + return theme.copyWith( + colorScheme: ColorScheme.fromSwatch().copyWith( + secondary: AppColors.primaryVariant, + ), + ); + } } extension TextAppearanceExt on BuildContext { - - TextStyle cardTextStyle(Color textColor) { - final base = theme.textTheme.headline5; - final screenWidth = MediaQuery.of(this).size.width; - - double fontSize = base.fontSize; - if (screenWidth > 360 && screenWidth <= 400) { - fontSize = 20.0; // Headline 6 Size - } else if (screenWidth > 300 && screenWidth <= 360) { - fontSize = 18.0; // Subtitle 1 - } else if (screenWidth <= 300) { - fontSize = 16.0; - } - - return base.copyWith( - color: textColor, - fontSize: fontSize - ); + TextStyle cardTextStyle(Color textColor) { + final base = theme.textTheme.headline5; + final screenWidth = MediaQuery.of(this).size.width; + + double? fontSize = base?.fontSize; + if (screenWidth > 360 && screenWidth <= 400) { + fontSize = 20.0; // Headline 6 Size + } else if (screenWidth > 300 && screenWidth <= 360) { + fontSize = 18.0; // Subtitle 1 + } else if (screenWidth <= 300) { + fontSize = 16.0; } + + return base!.copyWith(color: textColor, fontSize: fontSize); + } } extension ThemeExt on BuildContext { - - ThemeData get theme => Theme.of(this); - - Color get primaryColor { - var brightness = theme.brightness; - if (brightness == Brightness.dark) { - return AppColors.primaryVariant; - } else { - return AppColors.primary; - } + ThemeData get theme => Theme.of(this); + + Color get primaryColor { + var brightness = theme.brightness; + if (brightness == Brightness.dark) { + return AppColors.primaryVariant; + } else { + return AppColors.primary; } - - Color get colorOnCard { - var brightness = theme.brightness; - if (brightness == Brightness.dark) { - return Colors.white; - } else { - return Colors.black87; - } + } + + Color get colorOnCard { + var brightness = theme.brightness; + if (brightness == Brightness.dark) { + return Colors.white; + } else { + return Colors.black87; } - - Color get secondaryColorOnCard { - var brightness = theme.brightness; - if (brightness == Brightness.dark) { - return Colors.white54; - } else { - return Colors.black38; - } + } + + Color get secondaryColorOnCard { + var brightness = theme.brightness; + if (brightness == Brightness.dark) { + return Colors.white54; + } else { + return Colors.black38; } - - Color get tertiaryColorOnCard { - var brightness = theme.brightness; - if (brightness == Brightness.dark) { - return Colors.white38; - } else { - return Colors.black26; - } + } + + Color get tertiaryColorOnCard { + var brightness = theme.brightness; + if (brightness == Brightness.dark) { + return Colors.white38; + } else { + return Colors.black26; } - - Color get responseCardColor { - var brightness = theme.brightness; - if (brightness == Brightness.dark) { - return Colors.grey[600]; - } else { - return AppColors.responseCardBackground; - } + } + + Color get responseCardColor { + var brightness = theme.brightness; + if (brightness == Brightness.dark) { + return Colors.grey[600]!; + } else { + return AppColors.responseCardBackground; } - - Color get responseCardHandColor { - var brightness = theme.brightness; - if (brightness == Brightness.dark) { - return Colors.grey[500]; - } else { - return AppColors.responseCardBackground; - } + } + + Color get responseCardHandColor { + var brightness = theme.brightness; + if (brightness == Brightness.dark) { + return Colors.grey[500]!; + } else { + return AppColors.responseCardBackground; } - - Color get responseBorderColor { - var brightness = theme.brightness; - if (brightness == Brightness.dark) { - return Colors.white12; - } else { - return Colors.black12; - } + } + + Color get responseBorderColor { + var brightness = theme.brightness; + if (brightness == Brightness.dark) { + return Colors.white12; + } else { + return Colors.black12; } + } } diff --git a/lib/l10n/messages_all.dart b/lib/l10n/messages_all.dart index b285749..d8d43bb 100644 --- a/lib/l10n/messages_all.dart +++ b/lib/l10n/messages_all.dart @@ -27,16 +27,15 @@ MessageLookupByLibrary _findExact(String localeName) { case 'en': return messages_en.messages; default: - return null; + return messages_en.MessageLookup(); } } /// User programs should call this before using [localeName] for messages. Future initializeMessages(String localeName) async { var availableLocale = Intl.verifiedLocale( - localeName, - (locale) => _deferredLibraries[locale] != null, - onFailure: (_) => null); + localeName, (locale) => _deferredLibraries[locale] != null, + onFailure: (_) => null); if (availableLocale == null) { return new Future.value(false); } @@ -56,8 +55,8 @@ bool _messagesExistFor(String locale) { } MessageLookupByLibrary _findGeneratedMessagesFor(String locale) { - var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor, - onFailure: (_) => null); - if (actualLocale == null) return null; + var actualLocale = + Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => ""); + if (actualLocale == "") return messages_en.MessageLookup(); return _findExact(actualLocale); } diff --git a/lib/main.dart b/lib/main.dart index 38cbeb4..6aa72df 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -31,8 +31,8 @@ void main() async { runApp(buildRepositoryProvider(BlocProvider( create: (context) { return AuthenticationBloc( - userRepository: context.repository(), - preferences: AppPreferences() + userRepository: context.read(), + preferences: AppPreferences(), )..add(AppStarted()); }, child: App(), @@ -49,7 +49,8 @@ Widget buildRepositoryProvider(Widget child) { create: (context) => FirestoreCardsRepository(), ), RepositoryProvider( - create: (context) => FirestoreGameRepository(userRepository: context.repository()), + create: (context) => + FirestoreGameRepository(userRepository: context.read()), ) ], child: child, diff --git a/lib/ui/creategame/bloc/create_game_bloc.dart b/lib/ui/creategame/bloc/create_game_bloc.dart index 6e7fc7f..4c94021 100644 --- a/lib/ui/creategame/bloc/create_game_bloc.dart +++ b/lib/ui/creategame/bloc/create_game_bloc.dart @@ -7,10 +7,13 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kt_dart/kt.dart'; class CreateGameBloc extends Bloc { - final CardsRepository cardsRepository; - final GameRepository gameRepository; + final CardsRepository? cardsRepository; + final GameRepository? gameRepository; - CreateGameBloc(this.cardsRepository, this.gameRepository); + CreateGameBloc({ + this.cardsRepository, + this.gameRepository, + }) : super(CreateGameState()); @override CreateGameState get initialState => CreateGameState.empty(); @@ -38,62 +41,73 @@ class CreateGameBloc extends Bloc { Stream _mapScreenLoadedToState() async* { try { - var cardSets = await cardsRepository.getAvailableCardSets(); - var filteredCardSets = cardSets.where((cs) { - return (cs.source == "Developer" && AppPreferences().developerPackEnabled) || cs.source != "Developer"; + var cardSets = await cardsRepository?.getAvailableCardSets(); + var filteredCardSets = cardSets?.where((cs) { + return (cs.source == "Developer" && + AppPreferences().developerPackEnabled) || + cs.source != "Developer"; }).toList(); - yield state.copyWith(cardSets: filteredCardSets.toImmutableList(), isLoading: false); + yield state.copyWith( + cardSets: filteredCardSets?.toImmutableList(), isLoading: false); } catch (e) { yield state.copyWith(isLoading: false, error: e.toString()); } } - Stream _mapCardSetSelectedToState(CardSetSelected event) async* { + Stream _mapCardSetSelectedToState( + CardSetSelected event) async* { if (state.selectedSets.contains(event.cardSet)) { - yield state.copyWith(selectedSets: state.selectedSets.minusElement(event.cardSet).toSet()); + yield state.copyWith( + selectedSets: state.selectedSets.minusElement(event.cardSet).toSet()); } else { - yield state.copyWith(selectedSets: state.selectedSets.plusElement(event.cardSet).toSet()); + yield state.copyWith( + selectedSets: state.selectedSets.plusElement(event.cardSet).toSet()); } } - Stream _mapCardSourceSelectedToState(CardSourceSelected event) async* { - if (event.isAllChecked == null || !event.isAllChecked) { + Stream _mapCardSourceSelectedToState( + CardSourceSelected event) async* { + if (event.isAllChecked == false || !event.isAllChecked) { // Partial sets are selected, select all yield state.copyWith( - selectedSets: state.selectedSets.plus( - state.cardSets.filter((cs) => cs.source == event.source) - ).toSet() - ); + selectedSets: state.selectedSets + .plus(state.cardSets.filter((cs) => cs.source == event.source)) + .toSet()); } else if (event.isAllChecked) { // All sets selected, select none yield state.copyWith( - selectedSets: state.selectedSets.filter((s) => s.source != event.source).toSet() - ); + selectedSets: state.selectedSets + .filter((s) => s.source != event.source) + .toSet()); } } - Stream _mapChangePrizesToWinToState(ChangePrizesToWin event) async* { + Stream _mapChangePrizesToWinToState( + ChangePrizesToWin event) async* { AppPreferences().prizesToWin = event.prizesToWin; yield state.copyWith(prizesToWin: event.prizesToWin); } - Stream _mapChangePlayerLimitToState(ChangePlayerLimit event) async* { + Stream _mapChangePlayerLimitToState( + ChangePlayerLimit event) async* { AppPreferences().playerLimit = event.playerLimit; yield state.copyWith(playerLimit: event.playerLimit); } - Stream _mapChangePick2EnabledToState(ChangePick2Enabled event) async* { + Stream _mapChangePick2EnabledToState( + ChangePick2Enabled event) async* { yield state.copyWith(pick2Enabled: event.enabled); } - Stream _mapChangeDraw2Pick3EnabledToState(ChangeDraw2Pick3Enabled event) async* { + Stream _mapChangeDraw2Pick3EnabledToState( + ChangeDraw2Pick3Enabled event) async* { yield state.copyWith(draw2pick3Enabled: event.enabled); } Stream _mapCreateGameToState() async* { yield state.copyWith(isLoading: true, error: null); try { - var game = await gameRepository.createGame( + var game = await gameRepository?.createGame( state.selectedSets, prizesToWin: state.prizesToWin, playerLimit: state.playerLimit, diff --git a/lib/ui/creategame/bloc/create_game_state.dart b/lib/ui/creategame/bloc/create_game_state.dart index e2b82d8..86e4f0d 100644 --- a/lib/ui/creategame/bloc/create_game_state.dart +++ b/lib/ui/creategame/bloc/create_game_state.dart @@ -13,17 +13,17 @@ class CreateGameState { final bool pick2Enabled; final bool draw2pick3Enabled; - final Game createdGame; + final Game? createdGame; final bool isLoading; - final String error; + final String? error; - int get totalPrompts => selectedSets?.sumBy((cs) => cs.prompts ?? 0); - int get totalResponses => selectedSets?.sumBy((cs) => cs.responses ?? 0); + int get totalPrompts => selectedSets.sumBy((cs) => cs.prompts ?? 0); + int get totalResponses => selectedSets.sumBy((cs) => cs.responses ?? 0); CreateGameState({ - @required this.cardSets, - @required this.selectedSets, - @required this.isLoading, + required this.cardSets, + required this.selectedSets, + required this.isLoading, this.error, this.prizesToWin = 7, this.playerLimit = 15, @@ -43,28 +43,27 @@ class CreateGameState { } CreateGameState copyWith({ - KtList cardSets, - KtSet selectedSets, - int prizesToWin, - int playerLimit, - bool pick2Enabled, - bool draw2pick3Enabled, - bool isLoading, - String error, - Game createdGame, - bool overrideNull = false + KtList? cardSets, + KtSet? selectedSets, + int? prizesToWin, + int? playerLimit, + bool? pick2Enabled, + bool? draw2pick3Enabled, + bool? isLoading, + String? error, + Game? createdGame, + // bool overrideNull = false }) { return CreateGameState( - cardSets: cardSets ?? this.cardSets, - selectedSets: selectedSets ?? this.selectedSets, - prizesToWin: prizesToWin ?? this.prizesToWin, - playerLimit: playerLimit ?? this.playerLimit, - pick2Enabled: pick2Enabled ?? this.pick2Enabled, - draw2pick3Enabled: draw2pick3Enabled ?? this.draw2pick3Enabled, - isLoading: isLoading ?? this.isLoading, - error: overrideNull ? error : error ?? this.error, - createdGame: overrideNull ? createdGame : createdGame ?? this.createdGame - ); + cardSets: cardSets ?? this.cardSets, + selectedSets: selectedSets ?? this.selectedSets, + prizesToWin: prizesToWin ?? this.prizesToWin, + playerLimit: playerLimit ?? this.playerLimit, + pick2Enabled: pick2Enabled ?? this.pick2Enabled, + draw2pick3Enabled: draw2pick3Enabled ?? this.draw2pick3Enabled, + isLoading: isLoading ?? this.isLoading, + error: error ?? this.error, + createdGame: createdGame ?? this.createdGame); } @override diff --git a/lib/ui/creategame/create_game_screen.dart b/lib/ui/creategame/create_game_screen.dart index 880d06e..3bed0dc 100644 --- a/lib/ui/creategame/create_game_screen.dart +++ b/lib/ui/creategame/create_game_screen.dart @@ -1,13 +1,13 @@ import 'package:appsagainsthumanity/ui/creategame/bloc/bloc.dart'; import 'package:appsagainsthumanity/internal.dart'; -import 'package:appsagainsthumanity/ui/creategame/widgets/card_set_list.dart'; -import 'package:appsagainsthumanity/ui/creategame/widgets/game_options.dart'; +// import 'package:appsagainsthumanity/ui/creategame/widgets/card_set_list.dart'; +// import 'package:appsagainsthumanity/ui/creategame/widgets/game_options.dart'; import 'package:appsagainsthumanity/ui/routes.dart'; import 'package:appsagainsthumanity/ui/widgets/reponsive_widget_mediator.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:kt_dart/kt.dart'; +// import 'package:kt_dart/kt.dart'; import 'layouts/mobile_layout.dart'; import 'layouts/tablet_layout.dart'; @@ -23,38 +23,36 @@ class _CreateGameScreenState extends State { return AnnotatedRegion( value: SystemUiOverlayStyle.light, child: BlocProvider( - create: (context) => CreateGameBloc( - context.repository(), - context.repository(), - )..add(ScreenLoaded()), + create: (context) => CreateGameBloc()..add(ScreenLoaded()), child: MultiBlocListener( listeners: [ // Error Listener BlocListener( - condition: (previous, current) => current.error != previous.error, + listenWhen: (previous, current) => + current.error != previous.error, listener: (context, state) { - if (state.error != null) { + if (state.error != "") { context.scaffold ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [Text(state.error), Icon(Icons.error)], - ), - backgroundColor: Colors.redAccent, - ) - ); + ..showSnackBar(SnackBar( + content: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text(state.error!), Icon(Icons.error)], + ), + backgroundColor: Colors.redAccent, + )); } }, ), // New Game Listener BlocListener( - condition: (previous, current) => current.createdGame?.id != previous.createdGame?.id, + listenWhen: (previous, current) => + current.createdGame?.id != previous.createdGame?.id, listener: (context, state) { if (state.createdGame != null) { - Navigator.of(context).pushReplacement(GamePageRoute(state.createdGame)); + Navigator.of(context) + .pushReplacement(GamePageRoute(state.createdGame!)); } }, ), diff --git a/lib/ui/creategame/layouts/mobile_layout.dart b/lib/ui/creategame/layouts/mobile_layout.dart index b9c715c..ac79b70 100644 --- a/lib/ui/creategame/layouts/mobile_layout.dart +++ b/lib/ui/creategame/layouts/mobile_layout.dart @@ -8,7 +8,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kt_dart/kt.dart'; class MobileLayout extends StatelessWidget { - @override Widget build(BuildContext context) { return BlocBuilder( @@ -17,10 +16,14 @@ class MobileLayout extends StatelessWidget { length: 2, child: Scaffold( appBar: AppBar( - brightness: Brightness.dark, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarBrightness: Brightness.dark, + ), + // brightness: Brightness.dark, title: Text( context.strings.titleNewGame, - style: context.theme.textTheme.headline6.copyWith(color: Colors.white), + style: context.theme.textTheme.headline6! + .copyWith(color: Colors.white), ), leading: IconButton( icon: Icon(Icons.arrow_back, color: Colors.white), @@ -48,7 +51,9 @@ class MobileLayout extends StatelessWidget { child: Container( margin: const EdgeInsets.symmetric(horizontal: 16), child: Text( - state.isLoading ? "Loading..." : "${state.totalPrompts} Prompts ${state.totalResponses} Responses", + state.isLoading + ? "Loading..." + : "${state.totalPrompts} Prompts ${state.totalResponses} Responses", style: context.theme.textTheme.headline6, ), ), @@ -57,17 +62,20 @@ class MobileLayout extends StatelessWidget { ), ), ), - floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, - floatingActionButton: state.selectedSets.isNotEmpty() && !state.isLoading - ? FloatingActionButton( - child: Icon(Icons.check), - onPressed: () async { - // Start game? - Analytics().logSelectContent(contentType: 'action', itemId: 'create_game'); - context.bloc().add(CreateGame()); - }, - ) - : null, + floatingActionButtonLocation: + FloatingActionButtonLocation.endDocked, + floatingActionButton: + state.selectedSets.isNotEmpty() && !state.isLoading + ? FloatingActionButton( + child: Icon(Icons.check), + onPressed: () async { + // Start game? + Analytics().logSelectContent( + contentType: 'action', itemId: 'create_game'); + context.read().add(CreateGame()); + }, + ) + : null, body: TabBarView( children: [ CardSetList(state), diff --git a/lib/ui/creategame/layouts/tablet_layout.dart b/lib/ui/creategame/layouts/tablet_layout.dart index f862768..a3d9646 100644 --- a/lib/ui/creategame/layouts/tablet_layout.dart +++ b/lib/ui/creategame/layouts/tablet_layout.dart @@ -14,10 +14,14 @@ class TabletLayout extends StatelessWidget { builder: (context, state) { return Scaffold( appBar: AppBar( - brightness: Brightness.dark, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarBrightness: Brightness.dark, + ), + // brightness: Brightness.dark, title: Text( context.strings.titleNewGame, - style: context.theme.textTheme.headline6.copyWith(color: Colors.white), + style: context.theme.textTheme.headline6! + .copyWith(color: Colors.white), ), leading: IconButton( icon: Icon(Icons.arrow_back, color: Colors.white), @@ -50,16 +54,18 @@ class TabletLayout extends StatelessWidget { ), ), floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, - floatingActionButton: state.selectedSets.isNotEmpty() && !state.isLoading - ? FloatingActionButton( - child: Icon(Icons.check), - onPressed: () async { - // Start game? - Analytics().logSelectContent(contentType: 'action', itemId: 'create_game'); - context.bloc().add(CreateGame()); - }, - ) - : null, + floatingActionButton: + state.selectedSets.isNotEmpty() && !state.isLoading + ? FloatingActionButton( + child: Icon(Icons.check), + onPressed: () async { + // Start game? + Analytics().logSelectContent( + contentType: 'action', itemId: 'create_game'); + context.read().add(CreateGame()); + }, + ) + : null, body: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -73,14 +79,13 @@ class TabletLayout extends StatelessWidget { margin: const EdgeInsets.all(16), alignment: Alignment.topCenter, child: Material( - borderRadius: BorderRadius.circular(16), - elevation: 2, - color: context.responseCardColor, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: GameOptions(state), - ) - ), + borderRadius: BorderRadius.circular(16), + elevation: 2, + color: context.responseCardColor, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: GameOptions(state), + )), ), ), ), diff --git a/lib/ui/creategame/widgets/card_set_list.dart b/lib/ui/creategame/widgets/card_set_list.dart index 6b1cc30..1227be0 100644 --- a/lib/ui/creategame/widgets/card_set_list.dart +++ b/lib/ui/creategame/widgets/card_set_list.dart @@ -34,20 +34,28 @@ class CardSetList extends StatelessWidget { Widget _buildList(KtList sets, KtSet selected) { var groupedSets = sets.groupBy((cs) => cs.source); var widgets = groupedSets.keys.toList().sortedWith((a, b) { - var aW = keyWeight(a); - var bW = keyWeight(b); + var aW = keyWeight(a!); + var bW = keyWeight(b!); if (aW.compareTo(bW) == 0) { return a.compareTo(b); } else { return aW.compareTo(bW); } }).flatMap((key) { - var items = groupedSets.get(key).map((cs) => CardSetListItem(cs, selected.contains(cs))); - var allItemsSelected = items.all((i) => i.isSelected); - var noItemsSelected = items.none((i) => i.isSelected); + var items = groupedSets + .get(key) + ?.map((cs) => CardSetListItem(cs, selected.contains(cs))); + var allItemsSelected = items?.all((i) => i.isSelected); + var noItemsSelected = items?.none((i) => i.isSelected); return mutableListOf( - HeaderItem(key, allItemsSelected ? true : noItemsSelected ? false : null), - )..addAll(items); + HeaderItem( + key!, + allItemsSelected! + ? true + : noItemsSelected! + ? false + : null), + )..addAll(items!); }); return ListView.builder( diff --git a/lib/ui/creategame/widgets/card_set_list_item.dart b/lib/ui/creategame/widgets/card_set_list_item.dart index bbbba1e..1eb58df 100644 --- a/lib/ui/creategame/widgets/card_set_list_item.dart +++ b/lib/ui/creategame/widgets/card_set_list_item.dart @@ -14,19 +14,21 @@ class CardSetListItem extends StatelessWidget { @override Widget build(BuildContext context) { return ListTile( - title: Text(CahScrubber.scrub(cardSet.name)), + title: Text(CahScrubber.scrub(cardSet.name!)), contentPadding: const EdgeInsets.symmetric(horizontal: 6), leading: Checkbox( value: isSelected, activeColor: AppColors.primary, onChanged: (value) { - Analytics().logSelectContent(contentType: 'card_set', itemId: cardSet.name); - context.bloc().add(CardSetSelected(cardSet)); + Analytics() + .logSelectContent(contentType: 'card_set', itemId: cardSet.name); + context.read().add(CardSetSelected(cardSet)); }, ), onTap: () { - Analytics().logSelectContent(contentType: 'card_set', itemId: cardSet.name); - context.bloc().add(CardSetSelected(cardSet)); + Analytics() + .logSelectContent(contentType: 'card_set', itemId: cardSet.name); + context.read().add(CardSetSelected(cardSet)); }, ); } diff --git a/lib/ui/creategame/widgets/count_preference.dart b/lib/ui/creategame/widgets/count_preference.dart index a085d72..af07421 100644 --- a/lib/ui/creategame/widgets/count_preference.dart +++ b/lib/ui/creategame/widgets/count_preference.dart @@ -4,16 +4,16 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart class CountPreference extends StatelessWidget { final String title; - final String subtitle; + final String? subtitle; final int value; - final int min, max; + final int? min, max; final void Function(int) onValueChanged; CountPreference( this.value, { - @required this.title, + required this.title, this.subtitle, - @required this.onValueChanged, + required this.onValueChanged, this.min, this.max, }); @@ -22,7 +22,7 @@ class CountPreference extends StatelessWidget { Widget build(BuildContext context) { return ListTile( title: Text(title), - subtitle: subtitle != null ? Text(subtitle) : null, + subtitle: subtitle != "" ? Text(subtitle!) : const SizedBox(), contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16), trailing: Row( mainAxisSize: MainAxisSize.min, @@ -34,18 +34,20 @@ class CountPreference extends StatelessWidget { color: Colors.white, ), onPressed: () { - var newValue = (value - 1).clamp(min, max); + var newValue = (value - 1).clamp(min!, max!); onValueChanged(newValue); }, ), Container( width: 36, height: 36, - decoration: BoxDecoration(color: AppColors.primary, shape: BoxShape.circle), + decoration: + BoxDecoration(color: AppColors.primary, shape: BoxShape.circle), alignment: Alignment.center, child: Text( value.toString(), - style: context.theme.textTheme.subtitle1.copyWith(color: Colors.white), + style: context.theme.textTheme.subtitle1 + ?.copyWith(color: Colors.white), ), ), IconButton( @@ -55,7 +57,7 @@ class CountPreference extends StatelessWidget { color: Colors.white, ), onPressed: () { - var newValue = (value + 1).clamp(min, max); + var newValue = (value + 1).clamp(min!, max!); onValueChanged(newValue); }, ), diff --git a/lib/ui/creategame/widgets/game_options.dart b/lib/ui/creategame/widgets/game_options.dart index 9612689..d8aec97 100644 --- a/lib/ui/creategame/widgets/game_options.dart +++ b/lib/ui/creategame/widgets/game_options.dart @@ -20,8 +20,9 @@ class GameOptions extends StatelessWidget { min: 1, max: 15, onValueChanged: (value) { - Analytics().logSelectContent(contentType: 'game_option', itemId: 'prizes_to_win'); - context.bloc().add(ChangePrizesToWin(value)); + Analytics().logSelectContent( + contentType: 'game_option', itemId: 'prizes_to_win'); + context.read().add(ChangePrizesToWin(value)); }, ), CountPreference( @@ -31,8 +32,9 @@ class GameOptions extends StatelessWidget { min: 5, max: 30, onValueChanged: (value) { - Analytics().logSelectContent(contentType: 'game_option', itemId: 'player_limit'); - context.bloc().add(ChangePlayerLimit(value)); + Analytics().logSelectContent( + contentType: 'game_option', itemId: 'player_limit'); + context.read().add(ChangePlayerLimit(value)); }, ), SwitchListTile( @@ -41,8 +43,9 @@ class GameOptions extends StatelessWidget { activeColor: AppColors.primary, value: state.pick2Enabled, onChanged: (value) { - Analytics().logSelectContent(contentType: 'game_option', itemId: 'pick2'); - context.bloc().add(ChangePick2Enabled(value)); + Analytics() + .logSelectContent(contentType: 'game_option', itemId: 'pick2'); + context.read().add(ChangePick2Enabled(value)); }, ), SwitchListTile( @@ -51,8 +54,9 @@ class GameOptions extends StatelessWidget { activeColor: AppColors.primary, value: state.draw2pick3Enabled, onChanged: (value) { - Analytics().logSelectContent(contentType: 'game_option', itemId: 'draw2_pick3'); - context.bloc().add(ChangeDraw2Pick3Enabled(value)); + Analytics().logSelectContent( + contentType: 'game_option', itemId: 'draw2_pick3'); + context.read().add(ChangeDraw2Pick3Enabled(value)); }, ), ], diff --git a/lib/ui/creategame/widgets/header_item.dart b/lib/ui/creategame/widgets/header_item.dart index 58ced18..3242a24 100644 --- a/lib/ui/creategame/widgets/header_item.dart +++ b/lib/ui/creategame/widgets/header_item.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; class HeaderItem extends StatelessWidget { final String title; - final bool isChecked; + final bool? isChecked; HeaderItem(this.title, [this.isChecked]); @@ -14,8 +14,11 @@ class HeaderItem extends StatelessWidget { Widget build(BuildContext context) { return InkWell( onTap: () { - Analytics().logSelectContent(contentType: 'card_set_source', itemId: title); - context.bloc().add(CardSourceSelected(title, isChecked)); + Analytics() + .logSelectContent(contentType: 'card_set_source', itemId: title); + context + .read() + .add(CardSourceSelected(title, isChecked!)); }, child: Column( children: [ @@ -33,8 +36,11 @@ class HeaderItem extends StatelessWidget { value: isChecked, tristate: true, onChanged: (value) { - Analytics().logSelectContent(contentType: 'card_set_source', itemId: title); - context.bloc().add(CardSourceSelected(title, value)); + Analytics().logSelectContent( + contentType: 'card_set_source', itemId: title); + context + .read() + .add(CardSourceSelected(title, value!)); }, activeColor: AppColors.primary, checkColor: Colors.white, @@ -43,8 +49,8 @@ class HeaderItem extends StatelessWidget { child: Container( margin: const EdgeInsets.only(left: 16), child: Text( - CahScrubber.scrub(title) ?? "_UNKNOWN_", - style: context.theme.textTheme.subtitle2.copyWith( + CahScrubber.scrub(title) != "" ? "_UNKNOWN_" : "", + style: context.theme.textTheme.subtitle2?.copyWith( color: AppColors.primaryVariant, ), ), diff --git a/lib/ui/game/bloc/game_bloc.dart b/lib/ui/game/bloc/game_bloc.dart index faa930b..81c6f99 100644 --- a/lib/ui/game/bloc/game_bloc.dart +++ b/lib/ui/game/bloc/game_bloc.dart @@ -10,14 +10,28 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class GameBloc extends Bloc { - final Game initialGame; - final GameRepository gameRepository; + final Game? initialGame; + final GameRepository? gameRepository; StreamSubscription _gameSubscription; StreamSubscription _playersSubscription; StreamSubscription _downvoteSubscription; - GameBloc(this.initialGame, this.gameRepository); + GameBloc({ + this.initialGame, + this.gameRepository, + StreamSubscription? gameSubscription, + StreamSubscription? playersSubscription, + StreamSubscription? downvoteSubscription, + }) : _gameSubscription = gameSubscription!, + _playersSubscription = playersSubscription!, + _downvoteSubscription = downvoteSubscription!, + super(GameViewState()); + + // SignInBloc({ + // required UserRepository userRepository, + // }) : _userRepository = userRepository, + // super(SignInState.loading()) {} @override GameViewState get initialState => GameViewState(game: initialGame); @@ -59,20 +73,23 @@ class GameBloc extends Bloc { Stream _mapSubscribeToState(Subscribe event) async* { var user = FirebaseAuth.instance.currentUser; - add(UserUpdated(user.uid)); + add(UserUpdated(user!.uid)); - _gameSubscription?.cancel(); - _gameSubscription = gameRepository.observeGame(event.gameId).listen((game) { + _gameSubscription.cancel(); + _gameSubscription = + gameRepository!.observeGame(event.gameId).listen((game) { add(GameUpdated(game)); }); - - _playersSubscription?.cancel(); - _playersSubscription = gameRepository.observePlayers(event.gameId).listen((players) { + + _playersSubscription.cancel(); + _playersSubscription = + gameRepository!.observePlayers(event.gameId).listen((players) { add(PlayersUpdated(players)); }); - _downvoteSubscription?.cancel(); - _downvoteSubscription = gameRepository.observeDownvotes(event.gameId).listen((downvotes) { + _downvoteSubscription.cancel(); + _downvoteSubscription = + gameRepository!.observeDownvotes(event.gameId).listen((downvotes) { add(DownvotesUpdated(downvotes)); }); } @@ -82,26 +99,29 @@ class GameBloc extends Bloc { } Stream _mapGameUpdatedToState(GameUpdated event) async* { - yield state.copyWith(game: event.game, isLoading: false, isSubmitting: false); + yield state.copyWith( + game: event.game, isLoading: false, isSubmitting: false); } Stream _mapPlayersUpdatedToState(PlayersUpdated event) async* { yield state.copyWith(players: event.players, isLoading: false); } - Stream _mapDownvotesUpdatedToState(DownvotesUpdated event) async* { + Stream _mapDownvotesUpdatedToState( + DownvotesUpdated event) async* { yield state.copyWith(downvotes: event.downvotes, isLoading: false); } Stream _mapStartGameToState() async* { try { yield state.copyWith(isSubmitting: true); - await gameRepository.startGame(state.game.id); + await gameRepository!.startGame(state.game!.id!); } catch (e) { if (e is PlatformException) { print(e); if (e.code == 'functionsError') { - final Map details = Map.from(e.details); + final Map details = + Map.from(e.details); yield state.copyWith(error: details['message'], isSubmitting: false); } } @@ -114,7 +134,7 @@ class GameBloc extends Bloc { Stream _mapDownvoteToState() async* { try { - await gameRepository.downVoteCurrentPrompt(state.game.id); + await gameRepository!.downVoteCurrentPrompt(state.game!.id!); } catch (e) { yield state.copyWith(error: "$e"); } @@ -122,15 +142,17 @@ class GameBloc extends Bloc { Stream _mapWaveAtPlayerToState(WaveAtPlayer event) async* { try { - await gameRepository.waveAtPlayer(state.game.id, event.playerId, event.message); + await gameRepository! + .waveAtPlayer(state.game!.id!, event.playerId, event.message!); } catch (e) { yield state.copyWith(error: "$e"); } } - Stream _mapPickResponseCardToState(PickResponseCard event) async* { + Stream _mapPickResponseCardToState( + PickResponseCard event) async* { // Check prompt special to determine if we allow the user to pick tow - var special = promptSpecial(state.game.turn?.promptCard?.special); + var special = promptSpecial(state.game!.turn!.promptCard.special); if (special != null) { // With a special there is the opportunity to submit more than 1 card. If the user attempts to select more than // the allotted amount for a give prompt special, it will clear the selected and set the picked card as the only one @@ -139,8 +161,9 @@ class GameBloc extends Bloc { switch (special) { case PromptSpecial.pick2: // Selected size limit is 2 here. - if (currentSelection.length < 2) { - yield state.copyWith(selectedCards: currentSelection..add(event.card)); + if (currentSelection!.length < 2) { + yield state.copyWith( + selectedCards: currentSelection..add(event.card)); } else { yield state.copyWith(selectedCards: [event.card]); } @@ -148,12 +171,15 @@ class GameBloc extends Bloc { case PromptSpecial.draw2pick3: // Selected size limit is 3 here. The firebase function that deals with churning-turns will auto-matically // deal out an extra 2 cards to the user at turn start - if (currentSelection.length < 3) { - yield state.copyWith(selectedCards: currentSelection..add(event.card)); + if (currentSelection!.length < 3) { + yield state.copyWith( + selectedCards: currentSelection..add(event.card)); } else { yield state.copyWith(selectedCards: [event.card]); } break; + case PromptSpecial.derp: + break; } } else { // The lack of a special is an indication of PICK 1 only @@ -170,7 +196,7 @@ class GameBloc extends Bloc { if (responses != null && responses.isNotEmpty) { try { yield state.copyWith(isSubmitting: true); - await gameRepository.submitResponse(state.game.id, responses); + await gameRepository!.submitResponse(state.game!.id!, responses); yield state.copyWith(selectedCards: [], isSubmitting: false); } catch (e) { yield state.copyWith(error: "$e", isSubmitting: false); @@ -181,7 +207,7 @@ class GameBloc extends Bloc { Stream _mapPickWinnerToState(PickWinner event) async* { try { yield state.copyWith(isSubmitting: true); - await gameRepository.pickWinner(state.game.id, event.winningPlayerId); + await gameRepository!.pickWinner(state.game!.id!, event.winningPlayerId); yield state.copyWith(isSubmitting: false); } catch (e) { yield state.copyWith(error: "$e", isSubmitting: false); @@ -191,10 +217,15 @@ class GameBloc extends Bloc { Stream _mapKickWinnerToState(KickPlayer event) async* { try { yield state.copyWith(kickingPlayerId: event.playerId); - await gameRepository.kickPlayer(state.game.id, event.playerId); - yield state.copyWith(kickingPlayerId: null, overrideNull: true); + await gameRepository!.kickPlayer(state.game!.id!, event.playerId); + yield state.copyWith( + kickingPlayerId: null, + ); } catch (e) { - yield state.copyWith(error: "$e", kickingPlayerId: null, overrideNull: true); + yield state.copyWith( + error: "$e", + kickingPlayerId: null, + ); } } diff --git a/lib/ui/game/bloc/game_event.dart b/lib/ui/game/bloc/game_event.dart index cfc17ac..c3f8219 100644 --- a/lib/ui/game/bloc/game_event.dart +++ b/lib/ui/game/bloc/game_event.dart @@ -24,6 +24,7 @@ class Subscribe extends GameEvent { } class StartGame extends GameEvent {} + class ClearError extends GameEvent {} /// This event is triggered to update the user id in the game state @@ -69,12 +70,12 @@ class DownvotesUpdated extends GameEvent { List get props => [downvotes]; } -class DownvotePrompt extends GameEvent { } +class DownvotePrompt extends GameEvent {} @immutable class WaveAtPlayer extends GameEvent { final String playerId; - final String message; + final String? message; WaveAtPlayer(this.playerId, [this.message]); @@ -114,6 +115,6 @@ class KickPlayer extends GameEvent { List get props => [playerId]; } -class SubmitResponses extends GameEvent { } +class SubmitResponses extends GameEvent {} -class ClearSubmitting extends GameEvent { } +class ClearSubmitting extends GameEvent {} diff --git a/lib/ui/game/bloc/game_state.dart b/lib/ui/game/bloc/game_state.dart index 1e45c7d..ece2d34 100644 --- a/lib/ui/game/bloc/game_state.dart +++ b/lib/ui/game/bloc/game_state.dart @@ -7,27 +7,29 @@ import 'package:meta/meta.dart'; @immutable class GameViewState { /* FIELDS */ - final String userId; - final Game game; - final List players; - final List selectedCards; - final List downvotes; + final String? userId; + final Game? game; + final List? players; + final List? selectedCards; + final List? downvotes; final bool isSubmitting; - final String kickingPlayerId; + final String? kickingPlayerId; final bool isLoading; - final String error; + final String? error; /* PROPERTIES */ bool get isOurGame => userId == game?.ownerId; - Player get currentJudge => players?.firstWhere((p) => p.id == game.turn?.judgeId); - Player get lastJudge => players?.firstWhere((p) => !(game.turn?.winner?.responses?.containsKey(p.id) ?? true)); + Player get currentJudge => + players!.firstWhere((p) => p.id == game?.turn?.judgeId); + Player get lastJudge => players!.firstWhere( + (p) => !(game?.turn?.winner?.responses?.containsKey(p.id) ?? true)); /// Get the current prompt card text with any macros computed from the text string. For now this is just /// the a simple replace of the judge's name for its specific replacer text. /// TODO: Extract this into a tool that can take a configurable macro list for smart injecting text into prompts String get currentPromptText { - var prompt = game.turn?.promptCard; + var prompt = game?.turn?.promptCard; var judge = currentJudge; if (judge != null && prompt != null) { return prompt.text.replaceAll("{JUDGE_NAME}", judge.name); @@ -42,10 +44,10 @@ class GameViewState { /// the a simple replace of the judge's name for its specific replacer text. /// TODO: Extract this into a tool that can take a configurable macro list for smart injecting text into prompts String get lastPromptText { - var prompt = game.turn?.winner?.promptCard; + var prompt = game?.turn?.winner?.promptCard; var judge = lastJudge; - if (judge != null && prompt != null) { - return prompt.text.replaceAll("{JUDGE_NAME}", judge.name); + if (judge != {} && prompt != {}) { + return prompt!.text.replaceAll("{JUDGE_NAME}", judge.name); } else if (prompt != null) { return prompt.text; } else { @@ -53,22 +55,33 @@ class GameViewState { } } - Player get currentPlayer => players?.firstWhere((p) => p.id == userId); + Player get currentPlayer => players!.firstWhere((p) => p.id == userId); - Player get winner => players?.firstWhere((p) => p.id == game.winner); + Player get winner => players!.firstWhere((p) => p.id == game?.winner); - List get currentHand => currentPlayer?.hand?.where((c) => !selectedCards.contains(c))?.toList() ?? []; + List get currentHand => + currentPlayer.hand?.where((c) => !selectedCards!.contains(c)).toList() ?? + []; - bool get areWeJudge => currentJudge?.id == userId; + bool get areWeJudge => currentJudge.id == userId; - bool get haveWeSubmittedResponse => game.turn?.responses?.keys?.contains(userId) ?? false; + bool get haveWeSubmittedResponse => + game?.turn?.responses.keys.contains(userId) ?? false; bool get allResponsesSubmitted { - if (game.turn != null && game.turn.responses != null && players != null && players.isNotEmpty) { - var allPlayersExcludingJudgeAndInactive = - players.where((element) => element.id != game.turn.judgeId && element.isInactive != true).toList(); + if (game?.turn != {} && + game?.turn?.responses != {} && + players != {} && + players!.isNotEmpty) { + var allPlayersExcludingJudgeAndInactive = players! + .where((element) => + element.id != game?.turn?.judgeId && element.isInactive != true) + .toList(); for (var value in allPlayersExcludingJudgeAndInactive) { - if (game.turn.responses.keys.firstWhere((element) => element == value.id, orElse: () => null) == null) { + if (game?.turn?.responses.keys.firstWhere( + (element) => element == value.id, + orElse: () => "") == + null) { return false; } } @@ -79,15 +92,15 @@ class GameViewState { } bool get selectCardsMeetPromptRequirement { - PromptSpecial special = promptSpecial(game.turn?.promptCard?.special); - if (special != null) { + PromptSpecial special = promptSpecial(game!.turn!.promptCard.special); + if (special != {}) { if (special == PromptSpecial.pick2) { - return selectedCards.length == 2; + return selectedCards?.length == 2; } else if (special == PromptSpecial.draw2pick3) { - return selectedCards.length == 3; + return selectedCards?.length == 3; } } else { - return selectedCards.isNotEmpty; + return selectedCards!.isNotEmpty; } return false; } @@ -96,26 +109,26 @@ class GameViewState { this.userId, this.game, this.players, - List downvotes, - List selectedCards, + List? downvotes, + List? selectedCards, this.isSubmitting = false, this.isLoading = true, this.kickingPlayerId, this.error, - }) : selectedCards = selectedCards ?? [], + }) : selectedCards = selectedCards ?? [], downvotes = downvotes; GameViewState copyWith({ - String userId, - Game game, - List players, - List selectedCards, - List downvotes, - bool isSubmitting, - bool isLoading, - String kickingPlayerId, - String error, - bool overrideNull = false + String? userId, + Game? game, + List? players, + List? selectedCards, + List? downvotes, + bool? isSubmitting, + bool? isLoading, + String? kickingPlayerId, + String? error, + // bool overrideNull = false, }) { return GameViewState( userId: userId ?? this.userId, @@ -125,8 +138,8 @@ class GameViewState { selectedCards: selectedCards ?? this.selectedCards, isSubmitting: isSubmitting ?? this.isSubmitting, isLoading: isLoading ?? this.isLoading, - kickingPlayerId: overrideNull ? kickingPlayerId : kickingPlayerId ?? this.kickingPlayerId, - error: overrideNull ? error : error ?? this.error, + kickingPlayerId: kickingPlayerId ?? this.kickingPlayerId, + error: error ?? this.error, ); } diff --git a/lib/ui/game/game_screen.dart b/lib/ui/game/game_screen.dart index 542c008..f1ac964 100644 --- a/lib/ui/game/game_screen.dart +++ b/lib/ui/game/game_screen.dart @@ -13,7 +13,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; /// state. These states will be sub-divided into their own, bloc (or-not) controlled, /// screens/widgets class GameScreen extends StatelessWidget { - final Game game; GameScreen(this.game); @@ -21,25 +20,24 @@ class GameScreen extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => GameBloc(game, context.repository()) - ..add(Subscribe(game.id)), + create: (context) => GameBloc()..add(Subscribe(game.id!)), child: BlocBuilder( - condition: (previous, current) { - return previous.game.state != current.game.state || - (previous.game.state == GameState.waitingRoom && - current.game.state == GameState.waitingRoom && + buildWhen: (previous, current) { + return previous.game?.state != current.game?.state || + (previous.game?.state == GameState.waitingRoom && + current.game?.state == GameState.waitingRoom && previous.isSubmitting != current.isSubmitting); }, builder: (context, state) { - if (state.game.state == GameState.waitingRoom) { + if (state.game?.state == GameState.waitingRoom) { return state.isSubmitting ? StartingRoomScreen(state) : WaitingRoomScreen(); - } else if (state.game.state == GameState.starting) { + } else if (state.game?.state == GameState.starting) { return StartingRoomScreen(state); - } else if (state.game.state == GameState.inProgress) { + } else if (state.game?.state == GameState.inProgress) { return GamePlayScreen(state); - } else if (state.game.state == GameState.completed) { + } else if (state.game?.state == GameState.completed) { return CompletedGameScreen(); } else { return Container(); diff --git a/lib/ui/game/screens/complete/completed_game_screen.dart b/lib/ui/game/screens/complete/completed_game_screen.dart index 8b4bde2..e30fe7f 100644 --- a/lib/ui/game/screens/complete/completed_game_screen.dart +++ b/lib/ui/game/screens/complete/completed_game_screen.dart @@ -27,7 +27,7 @@ class CompletedGameScreen extends StatelessWidget { margin: const EdgeInsets.only(top: 88), child: Text( "Winner", - style: context.theme.textTheme.headline2.copyWith( + style: context.theme.textTheme.headline2?.copyWith( color: Colors.white, ), ), @@ -36,8 +36,8 @@ class CompletedGameScreen extends StatelessWidget { Container( margin: const EdgeInsets.only(top: 24), child: Text( - state.winner?.name ?? Player.DEFAULT_NAME, - style: context.theme.textTheme.headline4.copyWith( + state.winner.name != "" ? Player.DEFAULT_NAME : "", + style: context.theme.textTheme.headline4?.copyWith( color: Colors.white, ), ), @@ -47,34 +47,42 @@ class CompletedGameScreen extends StatelessWidget { Container( margin: const EdgeInsets.symmetric(horizontal: 24), width: double.maxFinite, - child: RaisedButton( + child: ElevatedButton( child: Text("NEW GAME"), - color: AppColors.primary, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8) + style: ElevatedButton.styleFrom( + primary: AppColors.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8)), ), onPressed: () { - Analytics().logSelectContent(contentType: 'game', itemId: 'create_new_game'); + Analytics().logSelectContent( + contentType: 'game', itemId: 'create_new_game'); Navigator.of(context).pushReplacement(MaterialPageRoute( - builder: (context) => CreateGameScreen() - )); + builder: (context) => CreateGameScreen())); }, ), ), Container( - margin: const EdgeInsets.only(top: 8, left: 24, right: 24, bottom: 48), + margin: const EdgeInsets.only( + top: 8, left: 24, right: 24, bottom: 48), width: double.maxFinite, - child: OutlineButton( + child: OutlinedButton( child: Text("QUIT"), - textColor: Colors.white, - borderSide: BorderSide(color: Colors.white), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + style: OutlinedButton.styleFrom( + textStyle: TextStyle( + color: Colors.white, + ), + primary: AppColors.primary, + // borderSide: BorderSide(color: Colors.white), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + // highlightedBorderColor: AppColors.primary, + // splashColor: AppColors.primary, ), - highlightedBorderColor: AppColors.primary, - splashColor: AppColors.primary, onPressed: () { - Analytics().logSelectContent(contentType: 'game', itemId: 'quit'); + Analytics() + .logSelectContent(contentType: 'game', itemId: 'quit'); Navigator.of(context).pop(); }, ), diff --git a/lib/ui/game/screens/gameplay/game_play_screen.dart b/lib/ui/game/screens/gameplay/game_play_screen.dart index 9effc38..46fcf30 100644 --- a/lib/ui/game/screens/gameplay/game_play_screen.dart +++ b/lib/ui/game/screens/gameplay/game_play_screen.dart @@ -73,12 +73,12 @@ class _GamePlayScreenState extends State { ), ), body: BlocListener( - condition: (previous, current) { - return current.game.turn?.winner != previous.game.turn?.winner; + listenWhen: (previous, current) { + return current.game?.turn?.winner != previous.game?.turn?.winner; }, listener: (context, state) { // Show bottom sheet modal for the winner - var turnWinner = state.game.turn?.winner; + var turnWinner = state.game?.turn?.winner; if (turnWinner != null) { print("Showing winner sheet"); _showWinnerBottomSheet(context, state); @@ -126,7 +126,7 @@ class _GamePlayScreenState extends State { maxChildSize: 0.97, builder: (context, scrollController) { return GameBottomSheet( - title: "Round ${state.game.round}", + title: "Round ${state.game?.round}", child: TurnWinnerSheet(state, scrollController), ); }, @@ -153,14 +153,14 @@ class _GamePlayScreenState extends State { height: 56, alignment: Alignment.center, child: Text( - widget.state.game.gid, - style: context.theme.textTheme.headline6.copyWith( + widget.state.game!.gid, + style: context.theme.textTheme.headline6?.copyWith( color: AppColors.primaryVariant, ), ), ) ], - child: PlayerList(widget.state.game, scrollController), + child: PlayerList(widget.state.game!, scrollController), ); }, ); diff --git a/lib/ui/game/screens/gameplay/widget/game_bottom_sheet.dart b/lib/ui/game/screens/gameplay/widget/game_bottom_sheet.dart index b36a911..abe4b61 100644 --- a/lib/ui/game/screens/gameplay/widget/game_bottom_sheet.dart +++ b/lib/ui/game/screens/gameplay/widget/game_bottom_sheet.dart @@ -2,13 +2,18 @@ import 'package:appsagainsthumanity/internal.dart'; import 'package:flutter/material.dart'; class GameBottomSheet extends StatelessWidget { - final String title; - final String subtitle; - final Widget child; - final List actions; - final EdgeInsets margin; + final String? title; + final String? subtitle; + final Widget? child; + final List? actions; + final EdgeInsets? margin; - GameBottomSheet({this.title, this.subtitle, @required this.child, this.actions, this.margin}); + GameBottomSheet( + {this.title, + this.subtitle, + @required this.child, + this.actions, + this.margin}); @override Widget build(BuildContext context) { @@ -32,7 +37,7 @@ class GameBottomSheet extends StatelessWidget { actions: actions, centerTitle: false, iconTheme: context.theme.iconTheme, - textTheme: context.theme.textTheme, + // textTheme: context.theme.textTheme, leading: Container( margin: const EdgeInsets.only(left: 8), child: IconButton( @@ -58,10 +63,10 @@ class GameBottomSheet extends StatelessWidget { margin: const EdgeInsets.only(left: 8), child: Column( children: [ - Text(title), + Text(title!), Text( - subtitle, - style: context.theme.textTheme.bodyText1.copyWith( + subtitle!, + style: context.theme.textTheme.bodyText1?.copyWith( color: Colors.black38, ), ) @@ -71,10 +76,10 @@ class GameBottomSheet extends StatelessWidget { } else { return Container( margin: const EdgeInsets.only(left: 8), - child: Text(title), + child: Text(title!), ); } } - return null; + return const SizedBox(); } } diff --git a/lib/ui/game/screens/gameplay/widget/game_status_title.dart b/lib/ui/game/screens/gameplay/widget/game_status_title.dart index 5db9ccf..3289396 100644 --- a/lib/ui/game/screens/gameplay/widget/game_status_title.dart +++ b/lib/ui/game/screens/gameplay/widget/game_status_title.dart @@ -13,13 +13,13 @@ class GameStatusTitle extends StatelessWidget { !state.allResponsesSubmitted ? "Waiting for responses" : "Judging", ); } else { - if (state.allResponsesSubmitted) { - return _buildText(context, "Waiting for judgement!"); - } else if (state.haveWeSubmittedResponse) { - return _buildText(context, "Waiting on other players"); - } else { - return Container(); - } + if (state.allResponsesSubmitted) { + return _buildText(context, "Waiting for judgement!"); + } else if (state.haveWeSubmittedResponse) { + return _buildText(context, "Waiting on other players"); + } else { + return Container(); + } } }); } @@ -27,7 +27,7 @@ class GameStatusTitle extends StatelessWidget { Widget _buildText(BuildContext context, String title) { return Text( title, - style: context.theme.textTheme.headline6.copyWith(color: Colors.white), + style: context.theme.textTheme.headline6?.copyWith(color: Colors.white), ); } } diff --git a/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart b/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart index 09da64b..011d378 100644 --- a/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart +++ b/lib/ui/game/screens/gameplay/widget/judge/judge_bar.dart @@ -26,15 +26,18 @@ class JudgeBar extends StatelessWidget { ); } - Widget _buildHeader(BuildContext context, Player player, {bool hasDownvoted = false}) { - var playerName = player.name ?? Player.DEFAULT_NAME; + Widget _buildHeader(BuildContext context, Player player, + {bool hasDownvoted = false}) { + var playerName = player.name != "" ? Player.DEFAULT_NAME : ""; if (playerName.trim().isEmpty) { playerName = Player.DEFAULT_NAME; } return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 12), title: Text(playerName), - subtitle: Text("Current judge", style: context.theme.textTheme.bodyText2.copyWith(color: Colors.white60)), + subtitle: Text("Current judge", + style: context.theme.textTheme.bodyText2 + ?.copyWith(color: Colors.white60)), leading: _buildJudgeAvatar(context, player), trailing: Row( mainAxisSize: MainAxisSize.min, @@ -47,8 +50,9 @@ class JudgeBar extends StatelessWidget { color: Colors.white, ), onPressed: () { - Analytics().logSelectContent(contentType: 'action', itemId: 'wave'); - context.bloc().add(WaveAtPlayer(player.id)); + Analytics() + .logSelectContent(contentType: 'action', itemId: 'wave'); + context.read().add(WaveAtPlayer(player.id)); }, ), Container( @@ -61,9 +65,10 @@ class JudgeBar extends StatelessWidget { ), onPressed: !hasDownvoted ? () { - Analytics().logSelectContent(contentType: 'action', itemId: 'downvote'); - context.bloc().add(DownvotePrompt()); - } + Analytics().logSelectContent( + contentType: 'action', itemId: 'downvote'); + context.read().add(DownvotePrompt()); + } : null, ) ], diff --git a/lib/ui/game/screens/gameplay/widget/judge/judge_dredd.dart b/lib/ui/game/screens/gameplay/widget/judge/judge_dredd.dart index 67579aa..10c7f05 100644 --- a/lib/ui/game/screens/gameplay/widget/judge/judge_dredd.dart +++ b/lib/ui/game/screens/gameplay/widget/judge/judge_dredd.dart @@ -23,7 +23,7 @@ class _JudgeDreddState extends State { @override void initState() { super.initState(); - controller.totalPageCount = widget.state.game.turn?.responses?.length ?? 0; + controller.totalPageCount = widget.state.game?.turn?.responses.length ?? 0; } @override @@ -53,16 +53,19 @@ class _JudgeDreddState extends State { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _buildPageButton(context: context, iconData: Icons.keyboard_arrow_left, isVisible: showLeft), - + _buildPageButton( + context: context, + iconData: Icons.keyboard_arrow_left, + isVisible: showLeft), if (widget.state.isSubmitting) _buildPickingWinnerIndicator(context), - if (!widget.state.isSubmitting) _buildPickWinnerButton(context), - _buildPageButton( - context: context, iconData: Icons.keyboard_arrow_right, isLeft: false, isVisible: showRight), + context: context, + iconData: Icons.keyboard_arrow_right, + isLeft: false, + isVisible: showRight), ], ); }), @@ -73,16 +76,20 @@ class _JudgeDreddState extends State { } Widget _buildPickWinnerButton(BuildContext context) { - return RaisedButton.icon( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - shape: StadiumBorder(), - color: AppColors.primary, + return ElevatedButton.icon( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + shape: StadiumBorder(), + primary: AppColors.primary, + ), onPressed: () async { var currentPlayerResponse = controller.currentPlayerResponse; if (currentPlayerResponse != null) { - Analytics().logSelectContent(contentType: 'judge', itemId: 'pick_winner'); + Analytics() + .logSelectContent(contentType: 'judge', itemId: 'pick_winner'); print("Winner selected! ${currentPlayerResponse.playerId}"); - context.bloc() + context + .read() .add(PickWinner(currentPlayerResponse.playerId)); } }, @@ -94,7 +101,7 @@ class _JudgeDreddState extends State { margin: const EdgeInsets.only(left: 16, right: 40), child: Text( "WINNER", - style: context.theme.textTheme.button.copyWith( + style: context.theme.textTheme.button?.copyWith( color: AppColors.colorOnPrimary, letterSpacing: 1, ), @@ -104,11 +111,12 @@ class _JudgeDreddState extends State { } Widget _buildPickingWinnerIndicator(BuildContext context) { - return RaisedButton.icon( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - shape: StadiumBorder(), - color: AppColors.primary, - disabledColor: AppColors.primary, + return ElevatedButton.icon( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + shape: StadiumBorder(), + primary: AppColors.primary, + ), icon: Container( width: 24, height: 24, @@ -120,18 +128,19 @@ class _JudgeDreddState extends State { margin: const EdgeInsets.only(left: 16, right: 40), child: Text( "SUBMITTING...", - style: context.theme.textTheme.button.copyWith( + style: context.theme.textTheme.button?.copyWith( color: AppColors.colorOnPrimary, letterSpacing: 1, ), ), ), + onPressed: () {}, ); } Widget _buildPageButton({ - @required BuildContext context, - @required IconData iconData, + required BuildContext context, + required IconData iconData, isVisible = true, isLeft = true, }) { @@ -159,10 +168,12 @@ class _JudgeDreddState extends State { onTap: isVisible ? () { if (isLeft) { - Analytics().logSelectContent(contentType: 'judge', itemId: 'previous_choice'); + Analytics().logSelectContent( + contentType: 'judge', itemId: 'previous_choice'); controller.prevPage(); } else { - Analytics().logSelectContent(contentType: 'judge', itemId: 'next_choice'); + Analytics().logSelectContent( + contentType: 'judge', itemId: 'next_choice'); controller.nextPage(); } } @@ -184,10 +195,11 @@ class _JudgeDreddState extends State { class JudgementController { static const double VIEWPORT_FRACTION = 0.945; - final PageController pageController = PageController(viewportFraction: VIEWPORT_FRACTION); + final PageController pageController = + PageController(viewportFraction: VIEWPORT_FRACTION); final StreamController _pageChanges = StreamController.broadcast(); - PlayerResponse _currentPlayerResponse; + late PlayerResponse _currentPlayerResponse; PlayerResponse get currentPlayerResponse => _currentPlayerResponse; diff --git a/lib/ui/game/screens/gameplay/widget/judge/judging_pager.dart b/lib/ui/game/screens/gameplay/widget/judge/judging_pager.dart index 853d9cb..6c666a6 100644 --- a/lib/ui/game/screens/gameplay/widget/judge/judging_pager.dart +++ b/lib/ui/game/screens/gameplay/widget/judge/judging_pager.dart @@ -13,31 +13,38 @@ class JudgingPager extends StatelessWidget { final JudgementController controller; JudgingPager({ - @required this.state, - @required this.controller, + required this.state, + required this.controller, }); @override Widget build(BuildContext context) { - var responses = state.game.turn?.responses ?? Map>(); - var playerResponses = responses.entries.map((e) => PlayerResponse(e.key, e.value.toList())).toList(); - playerResponses.shuffle(Random(state.game.round ?? DateTime.now().millisecondsSinceEpoch)); - controller.setCurrentResponse(playerResponses[0], 0, playerResponses.length); - return PageView.builder( - controller: controller.pageController, - itemCount: playerResponses.length, - itemBuilder: (context, index) { - var response = playerResponses[index]; - return Container( - margin: const EdgeInsets.symmetric(horizontal: 4), - child: buildResponseCardStack(response.responses), - ); - }, - onPageChanged: (index) { - Analytics().logSelectContent(contentType: 'judge', itemId: 'response_change_$index'); - var playerResponse = playerResponses[index]; - controller.setCurrentResponse(playerResponse, index, playerResponses.length); - }, - ); + var responses = + state.game?.turn?.responses ?? Map>(); + var playerResponses = responses.entries + .map((e) => PlayerResponse(e.key, e.value.toList())) + .toList(); + playerResponses.shuffle( + Random(state.game?.round ?? DateTime.now().millisecondsSinceEpoch)); + controller.setCurrentResponse( + playerResponses[0], 0, playerResponses.length); + return PageView.builder( + controller: controller.pageController, + itemCount: playerResponses.length, + itemBuilder: (context, index) { + var response = playerResponses[index]; + return Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + child: buildResponseCardStack(response.responses), + ); + }, + onPageChanged: (index) { + Analytics().logSelectContent( + contentType: 'judge', itemId: 'response_change_$index'); + var playerResponse = playerResponses[index]; + controller.setCurrentResponse( + playerResponse, index, playerResponses.length); + }, + ); } } diff --git a/lib/ui/game/screens/gameplay/widget/player_item.dart b/lib/ui/game/screens/gameplay/widget/player_item.dart index a140dca..c75aabb 100644 --- a/lib/ui/game/screens/gameplay/widget/player_item.dart +++ b/lib/ui/game/screens/gameplay/widget/player_item.dart @@ -22,7 +22,8 @@ class PlayerItem extends StatelessWidget { bool isSelf = false, this.isKicking = false, this.hasDownvoted = false, - }) : isSelf = isSelf || player.id == FirebaseConstants.DOCUMENT_RANDO_CARDRISSIAN; + }) : isSelf = + isSelf || player.id == FirebaseConstants.DOCUMENT_RANDO_CARDRISSIAN; @override Widget build(BuildContext context) { @@ -34,7 +35,7 @@ class PlayerItem extends StatelessWidget { direction: DismissDirection.endToStart, confirmDismiss: (direction) async { Analytics().logSelectContent(contentType: 'action', itemId: 'wave'); - context.bloc().add(WaveAtPlayer(player.id)); + context.read().add(WaveAtPlayer(player.id)); return false; }, background: Container( @@ -52,7 +53,7 @@ class PlayerItem extends StatelessWidget { } Widget _buildPlayerListTile(BuildContext context) { - var playerName = player.name ?? Player.DEFAULT_NAME; + var playerName = player.name == "" ? Player.DEFAULT_NAME : ""; if (playerName.trim().isEmpty) { playerName = Player.DEFAULT_NAME; } @@ -66,12 +67,12 @@ class PlayerItem extends StatelessWidget { onTap: () {}, title: Text( playerName, - style: context.theme.textTheme.subtitle1.copyWith(color: Colors.white), + style: context.theme.textTheme.subtitle1?.copyWith(color: Colors.white), ), subtitle: isJudge ? Text( "Judge", - style: context.theme.textTheme.bodyText2.copyWith( + style: context.theme.textTheme.bodyText2?.copyWith( color: AppColors.primaryVariant, ), ) @@ -97,7 +98,8 @@ class PlayerItem extends StatelessWidget { padding: const EdgeInsets.only(left: 16), child: Text( "${player.prizes?.length ?? 0}", - style: context.theme.textTheme.subtitle1.copyWith(fontWeight: FontWeight.w600), + style: context.theme.textTheme.subtitle1 + ?.copyWith(fontWeight: FontWeight.w600), ), ) ], @@ -130,8 +132,9 @@ class PlayerItem extends StatelessWidget { ), onPressed: () { // Kick Player - Analytics().logSelectContent(contentType: 'action', itemId: 'kick_player'); - context.bloc().add(KickPlayer(player.id)); + Analytics().logSelectContent( + contentType: 'action', itemId: 'kick_player'); + context.read().add(KickPlayer(player.id)); }, ), ), diff --git a/lib/ui/game/screens/gameplay/widget/player_list.dart b/lib/ui/game/screens/gameplay/widget/player_list.dart index e72d2b7..33ef8da 100644 --- a/lib/ui/game/screens/gameplay/widget/player_list.dart +++ b/lib/ui/game/screens/gameplay/widget/player_list.dart @@ -1,29 +1,32 @@ import 'package:appsagainsthumanity/data/features/game/model/game.dart'; +import 'package:appsagainsthumanity/data/features/game/model/game_state.dart'; import 'package:appsagainsthumanity/ui/game/bloc/bloc.dart'; import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/player_item.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class PlayerList extends StatelessWidget { - final Game initialGame; - final ScrollController scrollController; + final Game? initialGame; + final ScrollController? scrollController; PlayerList(this.initialGame, this.scrollController); @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => GameBloc(null, context.repository())..add(Subscribe(initialGame.id)), + create: (context) => GameBloc()..add(Subscribe(initialGame!.id!)), child: BlocBuilder( builder: (context, state) { - var players = (state.players ?? []).where((element) => element.isInactive != true).toList(); + var players = (state.players ?? []) + .where((element) => element.isInactive != true) + .toList(); return ListView.builder( controller: scrollController, padding: const EdgeInsets.symmetric(vertical: 8), itemCount: players.length, itemBuilder: (context, index) { var player = players[index]; - var isJudge = player.id == state.game.turn?.judgeId; + var isJudge = player.id == state.game?.turn?.judgeId; var hasDownvoted = state.downvotes?.contains(player.id) ?? false; return PlayerItem( player, diff --git a/lib/ui/game/screens/gameplay/widget/player_response_picker.dart b/lib/ui/game/screens/gameplay/widget/player_response_picker.dart index a160473..6099304 100644 --- a/lib/ui/game/screens/gameplay/widget/player_response_picker.dart +++ b/lib/ui/game/screens/gameplay/widget/player_response_picker.dart @@ -11,7 +11,8 @@ class PlayerResponsePicker extends StatefulWidget { } class _PlayerResponsePickerState extends State { - final PageController _pageController = PageController(viewportFraction: 0.945); + final PageController _pageController = + PageController(viewportFraction: 0.945); @override Widget build(BuildContext context) { @@ -19,7 +20,6 @@ class _PlayerResponsePickerState extends State { builder: (context, state) { // Determine if we need to show the response picker, or to hide this part if (!state.areWeJudge && !state.haveWeSubmittedResponse) { - // Get the player's current hand, omitting any card's they MAY have submitted var hand = state.currentHand.reversed.toList(); return Stack( @@ -65,11 +65,13 @@ class _PlayerResponsePickerState extends State { } Widget _buildSubmittingWidget(BuildContext context) { - return RaisedButton.icon( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - shape: StadiumBorder(), - color: AppColors.primary, - disabledColor: AppColors.primary, + return ElevatedButton.icon( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + shape: StadiumBorder(), + primary: AppColors.primary, + // disabledColor: AppColors.primary, + ), onPressed: null, icon: Container( width: 24, @@ -82,7 +84,7 @@ class _PlayerResponsePickerState extends State { margin: const EdgeInsets.only(left: 8, right: 20), child: Text( "SUBMITTING...", - style: context.theme.textTheme.button.copyWith( + style: context.theme.textTheme.button?.copyWith( color: AppColors.colorOnPrimary, letterSpacing: 1, ), @@ -92,13 +94,16 @@ class _PlayerResponsePickerState extends State { } Widget _buildSubmitCardsButton(BuildContext context) { - return RaisedButton.icon( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - shape: StadiumBorder(), - color: AppColors.primary, + return ElevatedButton.icon( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + shape: StadiumBorder(), + primary: AppColors.primary, + ), onPressed: () async { - Analytics().logSelectContent(contentType: 'action', itemId: 'submit_responses'); - context.bloc().add(SubmitResponses()); + Analytics().logSelectContent( + contentType: 'action', itemId: 'submit_responses'); + context.read().add(SubmitResponses()); }, icon: Icon( MdiIcons.uploadMultiple, @@ -108,7 +113,7 @@ class _PlayerResponsePickerState extends State { margin: const EdgeInsets.only(left: 8, right: 20), child: Text( "SUBMIT RESPONSE", - style: context.theme.textTheme.button.copyWith( + style: context.theme.textTheme.button?.copyWith( color: AppColors.colorOnPrimary, letterSpacing: 1, ), @@ -121,10 +126,10 @@ class _PlayerResponsePickerState extends State { class HandCard extends StatelessWidget { static const textPadding = 20.0; - final ResponseCard card; + final ResponseCard? card; HandCard({ - Key key, + Key? key, this.card, }) : super(key: key); @@ -152,14 +157,15 @@ class HandCard extends StatelessWidget { highlightColor: AppColors.primary.withOpacity(0.26), splashColor: AppColors.primary.withOpacity(0.26), onTap: () { - Analytics().logSelectContent(contentType: 'action', itemId: 'picked_response'); - context.bloc().add(PickResponseCard(card)); + Analytics().logSelectContent( + contentType: 'action', itemId: 'picked_response'); + context.read().add(PickResponseCard(card!)); }, child: Column( children: [ - _buildText(context, card.text), + _buildText(context, card!.text), Expanded( - child: _buildCaptionText(context, card.set), + child: _buildCaptionText(context, card!.set), ), ], ), @@ -187,7 +193,7 @@ class HandCard extends StatelessWidget { child: Text( text, textAlign: TextAlign.end, - style: context.theme.textTheme.caption.copyWith( + style: context.theme.textTheme.caption?.copyWith( color: context.secondaryColorOnCard, fontStyle: FontStyle.italic, ), diff --git a/lib/ui/game/screens/gameplay/widget/prompt_container.dart b/lib/ui/game/screens/gameplay/widget/prompt_container.dart index 83b4dfa..fd7573b 100644 --- a/lib/ui/game/screens/gameplay/widget/prompt_container.dart +++ b/lib/ui/game/screens/gameplay/widget/prompt_container.dart @@ -15,16 +15,17 @@ class PromptContainer extends StatelessWidget { Widget build(BuildContext context) { // We only want this block builder to update when the prompt changes return BlocBuilder( - condition: (previous, current) { - return previous.game.turn?.promptCard != current.game.turn?.promptCard; + buildWhen: (previous, current) { + return previous.game?.turn?.promptCard != + current.game?.turn?.promptCard; }, builder: (context, state) { - var prompt = state.game.turn?.promptCard; + var prompt = state.game?.turn?.promptCard; return Container( margin: const EdgeInsets.only(top: 8), child: Column( children: [ - _buildPromptSpecial(context, prompt), + _buildPromptSpecial(context, prompt!), Expanded( child: Stack( children: [ @@ -70,7 +71,7 @@ class PromptContainer extends StatelessWidget { if (state.haveWeSubmittedResponse) { return WaitingPlayerResponses(state); } else { - var responseCardStack = buildResponseCardStack(state.selectedCards); + var responseCardStack = buildResponseCardStack(state.selectedCards!); if (responseCardStack != null) { return Dismissible( key: Key("responses"), @@ -83,8 +84,9 @@ class PromptContainer extends StatelessWidget { alignment: Alignment.topCenter, child: Text( "Clear Responses".toUpperCase(), - style: context.theme.textTheme.subtitle1 - .copyWith(color: context.primaryColor, fontWeight: FontWeight.w600), + style: context.theme.textTheme.subtitle1!.copyWith( + color: context.primaryColor, + fontWeight: FontWeight.w600), ), ), Container( @@ -97,8 +99,9 @@ class PromptContainer extends StatelessWidget { ], ), onDismissed: (direction) { - Analytics().logSelectContent(contentType: 'action', itemId: 'clear_choices'); - context.bloc().add(ClearPickedResponseCards()); + Analytics().logSelectContent( + contentType: 'action', itemId: 'clear_choices'); + context.read().add(ClearPickedResponseCards()); }, child: Container( margin: const EdgeInsets.symmetric(horizontal: 16), @@ -114,14 +117,16 @@ class PromptContainer extends StatelessWidget { } Widget _buildPromptSpecial(BuildContext context, PromptCard promptCard) { - if (promptCard != null && promptCard.special != null && promptCard.special.isNotEmpty) { + if (promptCard != null && + promptCard.special != "" && + promptCard.special.isNotEmpty) { return Container( height: 36, alignment: Alignment.centerRight, margin: const EdgeInsets.symmetric(horizontal: 16), child: Text( promptCard.special.toUpperCase(), - style: context.theme.textTheme.subtitle2.copyWith( + style: context.theme.textTheme.subtitle2?.copyWith( color: Colors.white, ), ), @@ -151,15 +156,17 @@ class PromptContainer extends StatelessWidget { Widget _buildPromptText(BuildContext context, GameViewState state) { return GestureDetector( onLongPress: () { - Analytics().logSelectContent(contentType: 'action', itemId: 'view_prompt_source'); - Scaffold.of(context).showSnackBar(SnackBar( - content: Text(state.game?.turn?.promptCard?.set ?? ""), + Analytics().logSelectContent( + contentType: 'action', itemId: 'view_prompt_source'); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(state.game?.turn?.promptCard.set ?? ""), behavior: SnackBarBehavior.floating, )); }, child: Container( width: double.maxFinite, - margin: const EdgeInsets.symmetric(vertical: textPadding, horizontal: textPadding + 16), + margin: const EdgeInsets.symmetric( + vertical: textPadding, horizontal: textPadding + 16), child: BlocBuilder( builder: (context, state) { return Text( diff --git a/lib/ui/game/screens/gameplay/widget/re_deal_button.dart b/lib/ui/game/screens/gameplay/widget/re_deal_button.dart index abcc7e1..da4e783 100644 --- a/lib/ui/game/screens/gameplay/widget/re_deal_button.dart +++ b/lib/ui/game/screens/gameplay/widget/re_deal_button.dart @@ -10,7 +10,9 @@ class ReDealButton extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - if (!state.areWeJudge && !state.haveWeSubmittedResponse && (state.currentPlayer?.prizes?.length ?? 0) > 0) { + if (!state.areWeJudge && + !state.haveWeSubmittedResponse && + (state.currentPlayer.prizes?.length ?? 0) > 0) { return IconButton( icon: Icon(MdiIcons.cardsVariant), tooltip: "Re-deal your hand", @@ -27,22 +29,31 @@ class ReDealButton extends StatelessWidget { content: RichText( text: TextSpan(text: 'Spend ', children: [ TextSpan( - text: '1 of ${state.currentPlayer.prizes.length} prize cards', + text: + '1 of ${state.currentPlayer.prizes?.length} prize cards', style: TextStyle(fontWeight: FontWeight.bold)), TextSpan(text: ' to deal you a new hand?') ]), ), actions: [ - FlatButton( - child: Text('CANCEL'), - textColor: AppColors.secondary, + TextButton( + child: Text( + 'CANCEL', + style: TextStyle( + color: AppColors.secondary, + ), + ), onPressed: () { Navigator.of(context).pop(false); }, ), - FlatButton( - child: Text('DEAL'), - textColor: AppColors.secondary, + TextButton( + child: Text( + 'DEAL', + style: TextStyle( + color: AppColors.secondary, + ), + ), onPressed: () { Navigator.of(context).pop(true); }, @@ -51,8 +62,11 @@ class ReDealButton extends StatelessWidget { ); }); if (result ?? false) { - Analytics().logSelectContent(contentType: 'action', itemId: 'redeal_hand'); - await context.repository().reDealHand(state.game.id); + Analytics().logSelectContent( + contentType: 'action', itemId: 'redeal_hand'); + await context + .read() + .reDealHand(state.game!.id!); } }, ); diff --git a/lib/ui/game/screens/gameplay/widget/response_card_view.dart b/lib/ui/game/screens/gameplay/widget/response_card_view.dart index 2079315..bd4f1ea 100644 --- a/lib/ui/game/screens/gameplay/widget/response_card_view.dart +++ b/lib/ui/game/screens/gameplay/widget/response_card_view.dart @@ -4,13 +4,13 @@ import 'package:appsagainsthumanity/internal.dart'; class ResponseCardView extends StatelessWidget { final ResponseCard card; - final Widget child; + final Widget? child; static const textPadding = 20.0; ResponseCardView({ - Key key, - @required this.card, + Key? key, + required this.card, this.child, }) : super(key: key); @@ -37,7 +37,7 @@ class ResponseCardView extends StatelessWidget { _buildText(context, card.text), if (child != null) Expanded( - child: child, + child: child ?? const SizedBox(), ), ], ), @@ -57,7 +57,7 @@ class ResponseCardView extends StatelessWidget { } } -Widget buildResponseCardStack(List cards, {Widget lastChild}) { +Widget buildResponseCardStack(List cards, {Widget? lastChild}) { if (cards.isNotEmpty) { var nextCard = cards.first; var remaining = cards.sublist(1); @@ -65,10 +65,10 @@ Widget buildResponseCardStack(List cards, {Widget lastChild}) { key: ValueKey(nextCard), card: nextCard, child: remaining.isNotEmpty - ? buildResponseCardStack(remaining, lastChild: lastChild) - : lastChild, + ? buildResponseCardStack(remaining, lastChild: lastChild) + : lastChild, ); } else { - return null; + return const SizedBox(); } } diff --git a/lib/ui/game/screens/gameplay/widget/waiting_player_responses.dart b/lib/ui/game/screens/gameplay/widget/waiting_player_responses.dart index d633ef5..a53418d 100644 --- a/lib/ui/game/screens/gameplay/widget/waiting_player_responses.dart +++ b/lib/ui/game/screens/gameplay/widget/waiting_player_responses.dart @@ -13,8 +13,10 @@ class WaitingPlayerResponses extends StatelessWidget { @override Widget build(BuildContext context) { var players = state.players?.where((element) { - return element.id != state.game.turn?.judgeId && element.isInactive != true; - })?.toList() ?? []; + return element.id != state.game?.turn?.judgeId && + element.isInactive != true; + }).toList() ?? + []; var columnCount = 3; if (MediaQuery.of(context).size.width >= 600) { @@ -36,7 +38,9 @@ class WaitingPlayerResponses extends StatelessWidget { itemCount: players.length, itemBuilder: (context, index) { var player = players[index]; - var hasSubmittedResponse = state.game.turn?.responses?.containsKey(player.id) ?? false; + var hasSubmittedResponse = + state.game?.turn?.responses.containsKey(player.id) ?? + false; return PlayerResponseCard(player, hasSubmittedResponse); }), ), @@ -75,7 +79,7 @@ class PlayerResponseCard extends StatelessWidget { textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, - style: context.theme.textTheme.bodyText2.copyWith( + style: context.theme.textTheme.bodyText2?.copyWith( color: context.colorOnCard, ), ), diff --git a/lib/ui/game/screens/gameplay/widget/winning/other_responses_pager.dart b/lib/ui/game/screens/gameplay/widget/winning/other_responses_pager.dart index 412a04b..090f22e 100644 --- a/lib/ui/game/screens/gameplay/widget/winning/other_responses_pager.dart +++ b/lib/ui/game/screens/gameplay/widget/winning/other_responses_pager.dart @@ -24,7 +24,8 @@ class OtherResponsesPager extends StatefulWidget { class _OtherResponsesPagerState extends State { static const double VIEWPORT_FRACTION = 0.93; - final PageController pageController = PageController(viewportFraction: VIEWPORT_FRACTION); + final PageController pageController = + PageController(viewportFraction: VIEWPORT_FRACTION); @override void dispose() { @@ -34,11 +35,14 @@ class _OtherResponsesPagerState extends State { @override Widget build(BuildContext context) { - var _responses = widget.responses ?? Map>(); - var playerResponses = _responses.entries.map((e) => PlayerResponse(e.key, e.value.toList())) + var _responses = + widget.responses != {} ? Map>() : {}; + var playerResponses = _responses.entries + .map((e) => PlayerResponse(e.key, e.value.toList())) .where((element) => element.playerId != widget.winningPlayerId) .toList(); - playerResponses.shuffle(Random(widget.gameRound ?? DateTime.now().millisecondsSinceEpoch)); + playerResponses.shuffle(Random( + widget.gameRound >= 0 ? DateTime.now().millisecondsSinceEpoch : 0)); return PageView.builder( controller: pageController, itemCount: playerResponses.length, diff --git a/lib/ui/game/screens/gameplay/widget/winning/prompt_card_view.dart b/lib/ui/game/screens/gameplay/widget/winning/prompt_card_view.dart index 46844b7..fef9400 100644 --- a/lib/ui/game/screens/gameplay/widget/winning/prompt_card_view.dart +++ b/lib/ui/game/screens/gameplay/widget/winning/prompt_card_view.dart @@ -1,12 +1,12 @@ import 'package:appsagainsthumanity/data/features/cards/model/prompt_card.dart'; import 'package:appsagainsthumanity/ui/game/bloc/bloc.dart'; import 'package:appsagainsthumanity/internal.dart'; -import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/judge/judge_dredd.dart'; -import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/response_card_view.dart'; -import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/waiting_player_responses.dart'; +// import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/judge/judge_dredd.dart'; +// import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/response_card_view.dart'; +// import 'package:appsagainsthumanity/ui/game/screens/gameplay/widget/waiting_player_responses.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +// import 'package:flutter_bloc/flutter_bloc.dart'; +// import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; class PromptCardView extends StatelessWidget { static const textPadding = 20.0; @@ -15,13 +15,16 @@ class PromptCardView extends StatelessWidget { final Widget child; final EdgeInsets margin; - PromptCardView({@required this.state, @required this.child, EdgeInsets margin}) : - margin = margin ?? const EdgeInsets.symmetric(horizontal: 16); + PromptCardView({ + required this.state, + required this.child, + EdgeInsets? margin, + }) : margin = margin ?? const EdgeInsets.symmetric(horizontal: 16); @override Widget build(BuildContext context) { // We only want this block builder to update when the prompt changes - var prompt = state.game.turn?.winner?.promptCard; + var prompt = state.game?.turn?.winner?.promptCard; return Container( margin: const EdgeInsets.only(top: 8), child: Column( @@ -31,7 +34,7 @@ class PromptCardView extends StatelessWidget { children: [ _buildPromptBackground( context: context, - card: prompt, + card: prompt!, ), Column( children: [ @@ -49,7 +52,8 @@ class PromptCardView extends StatelessWidget { ); } - Widget _buildPromptBackground({@required BuildContext context, @required PromptCard card}) { + Widget _buildPromptBackground( + {@required BuildContext? context, @required PromptCard? card}) { return Container( margin: margin, child: Material( @@ -69,15 +73,18 @@ class PromptCardView extends StatelessWidget { Widget _buildPromptText(BuildContext context, GameViewState state) { return GestureDetector( onLongPress: () { - Analytics().logSelectContent(contentType: 'action', itemId: 'view_prompt_source'); - Scaffold.of(context).showSnackBar(SnackBar( + Analytics().logSelectContent( + contentType: 'action', itemId: 'view_prompt_source'); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(state.game?.turn?.winner?.promptCard?.set ?? ""), behavior: SnackBarBehavior.floating, )); }, child: Container( width: double.maxFinite, - margin: const EdgeInsets.symmetric(vertical: textPadding, horizontal: textPadding).add(margin), + margin: const EdgeInsets.symmetric( + vertical: textPadding, horizontal: textPadding) + .add(margin), child: Text( state.lastPromptText, style: context.cardTextStyle(Colors.white), diff --git a/lib/ui/game/screens/gameplay/widget/winning/turn_winner_sheet.dart b/lib/ui/game/screens/gameplay/widget/winning/turn_winner_sheet.dart index 2130546..029d61b 100644 --- a/lib/ui/game/screens/gameplay/widget/winning/turn_winner_sheet.dart +++ b/lib/ui/game/screens/gameplay/widget/winning/turn_winner_sheet.dart @@ -9,15 +9,16 @@ import 'package:appsagainsthumanity/ui/widgets/player_circle_avatar.dart'; import 'package:flutter/material.dart'; class TurnWinnerSheet extends StatelessWidget { - final GameViewState state; - final TurnWinner turnWinner; - final ScrollController scrollController; + final GameViewState? state; + final TurnWinner? turnWinner; + final ScrollController? scrollController; - TurnWinnerSheet(this.state, this.scrollController) : turnWinner = state.game.turn.winner; + TurnWinnerSheet(this.state, this.scrollController) + : turnWinner = state?.game?.turn?.winner; @override Widget build(BuildContext context) { - var playerName = turnWinner.playerName ?? Player.DEFAULT_NAME; + var playerName = turnWinner?.playerName ?? Player.DEFAULT_NAME; if (playerName.trim().isEmpty) { playerName = Player.DEFAULT_NAME; } @@ -32,7 +33,7 @@ class TurnWinnerSheet extends StatelessWidget { margin: const EdgeInsets.only(bottom: 16), child: Text( "Winner!", - style: context.theme.textTheme.headline3.copyWith( + style: context.theme.textTheme.headline3?.copyWith( color: Colors.white, ), ), @@ -44,7 +45,7 @@ class TurnWinnerSheet extends StatelessWidget { margin: const EdgeInsets.only(top: 16), child: Text( playerName, - style: context.theme.textTheme.headline5.copyWith( + style: context.theme.textTheme.headline5?.copyWith( color: Colors.white, ), ), @@ -53,12 +54,12 @@ class TurnWinnerSheet extends StatelessWidget { height: 850, margin: const EdgeInsets.only(top: 24), child: PromptCardView( - state: state, + state: state!, margin: const EdgeInsets.symmetric(horizontal: 32), child: Container( margin: const EdgeInsets.symmetric(horizontal: 32), child: buildResponseCardStack( - turnWinner.response, + turnWinner!.response!, lastChild: Column( children: [ Divider(), @@ -67,7 +68,7 @@ class TurnWinnerSheet extends StatelessWidget { alignment: Alignment.centerLeft, child: Text( "Player Responses".toUpperCase(), - style: context.theme.textTheme.subtitle2.copyWith( + style: context.theme.textTheme.subtitle2?.copyWith( color: AppColors.primaryVariant, ), ), @@ -76,9 +77,9 @@ class TurnWinnerSheet extends StatelessWidget { child: Container( margin: const EdgeInsets.only(top: 16), child: OtherResponsesPager( - turnWinner.playerId, - state.game.round - 1, - turnWinner.responses, + turnWinner!.playerId!, + state!.game!.round - 1, + turnWinner!.responses!, ), ), ), @@ -98,10 +99,10 @@ class TurnWinnerSheet extends StatelessWidget { return PlayerCircleAvatar( radius: 40, player: Player( - id: turnWinner.playerId, - name: turnWinner.playerName, - avatarUrl: turnWinner.playerAvatarUrl, - isRandoCardrissian: turnWinner.isRandoCardrissian, + id: turnWinner!.playerId!, + name: turnWinner!.playerName!, + avatarUrl: turnWinner!.playerAvatarUrl!, + isRandoCardrissian: turnWinner!.isRandoCardrissian!, ), ); } diff --git a/lib/ui/game/screens/starting/starting_room_screen.dart b/lib/ui/game/screens/starting/starting_room_screen.dart index 1327bdb..e451fae 100644 --- a/lib/ui/game/screens/starting/starting_room_screen.dart +++ b/lib/ui/game/screens/starting/starting_room_screen.dart @@ -1,5 +1,6 @@ import 'package:appsagainsthumanity/ui/game/bloc/bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:appsagainsthumanity/internal.dart'; class StartingRoomScreen extends StatelessWidget { @@ -11,8 +12,11 @@ class StartingRoomScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - brightness: Brightness.dark, - textTheme: context.theme.textTheme, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarBrightness: Brightness.dark, + ), + // brightness: Brightness.dark, + // textTheme: context.theme.textTheme, iconTheme: context.theme.iconTheme, title: Text("Game is starting..."), bottom: PreferredSize( @@ -30,13 +34,13 @@ class StartingRoomScreen extends StatelessWidget { children: [ Text( "Game ID", - style: context.theme.textTheme.bodyText1.copyWith( + style: context.theme.textTheme.bodyText1?.copyWith( color: Colors.white70, ), ), Text( - state.game.gid, - style: context.theme.textTheme.headline4.copyWith( + state.game!.gid, + style: context.theme.textTheme.headline4?.copyWith( color: context.primaryColor, fontWeight: FontWeight.bold, ), diff --git a/lib/ui/game/screens/waiting/waiting_room_screen.dart b/lib/ui/game/screens/waiting/waiting_room_screen.dart index e3af52c..2366bfd 100644 --- a/lib/ui/game/screens/waiting/waiting_room_screen.dart +++ b/lib/ui/game/screens/waiting/waiting_room_screen.dart @@ -1,4 +1,3 @@ - import 'package:appsagainsthumanity/data/features/game/game_repository.dart'; import 'package:appsagainsthumanity/data/features/game/model/player.dart'; import 'package:appsagainsthumanity/internal.dart'; @@ -7,6 +6,7 @@ import 'package:appsagainsthumanity/ui/game/bloc/bloc.dart'; import 'package:appsagainsthumanity/ui/widgets/player_circle_avatar.dart'; import 'package:clipboard/clipboard.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:share/share.dart'; @@ -16,7 +16,7 @@ class WaitingRoomScreen extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( - bloc: context.bloc(), + bloc: context.read(), builder: (context, state) { return _buildScaffold(context, state); }, @@ -26,8 +26,11 @@ class WaitingRoomScreen extends StatelessWidget { Widget _buildScaffold(BuildContext context, GameViewState state) { return Scaffold( appBar: AppBar( - brightness: Brightness.dark, - textTheme: context.theme.textTheme, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarBrightness: Brightness.dark, + ), + // brightness: Brightness.dark, + // textTheme: context.theme.textTheme, iconTheme: context.theme.iconTheme, title: Text("Waiting for players"), bottom: PreferredSize( @@ -45,13 +48,13 @@ class WaitingRoomScreen extends StatelessWidget { children: [ Text( "Game ID", - style: context.theme.textTheme.bodyText1.copyWith( + style: context.theme.textTheme.bodyText1?.copyWith( color: Colors.white70, ), ), Text( - state.game.gid, - style: context.theme.textTheme.headline4.copyWith( + state.game!.gid, + style: context.theme.textTheme.headline4?.copyWith( color: context.primaryColor, fontWeight: FontWeight.bold, ), @@ -59,7 +62,6 @@ class WaitingRoomScreen extends StatelessWidget { ], ), ), - Expanded( child: Row( mainAxisSize: MainAxisSize.max, @@ -70,18 +72,22 @@ class WaitingRoomScreen extends StatelessWidget { child: OutlinedButton( child: Text("INVITE"), style: OutlinedButton.styleFrom( - primary: context.primaryColor, - textStyle: context.theme.textTheme.button.copyWith( - color: context.primaryColor, - ), - side: BorderSide(color: context.primaryColor), - padding: const EdgeInsets.all(16) - ), + primary: context.primaryColor, + textStyle: + context.theme.textTheme.button?.copyWith( + color: context.primaryColor, + ), + side: BorderSide(color: context.primaryColor), + padding: const EdgeInsets.all(16)), // highlightedBorderColor: context.primaryColor, // splashColor: context.primaryColor.withOpacity(0.40), onPressed: () async { - Analytics().logShare(contentType: 'game', itemId: 'invite', method: 'dynamic_link'); - var link = await DynamicLinks.createLink(state.game.id); + Analytics().logShare( + contentType: 'game', + itemId: 'invite', + method: 'dynamic_link'); + var link = + await DynamicLinks.createLink(state.game!.id!); _shareLink(context, link.toString()); }, ), @@ -110,20 +116,20 @@ class WaitingRoomScreen extends StatelessWidget { label: Text("START GAME"), backgroundColor: AppColors.primary, onPressed: () async { - Analytics().logSelectContent(contentType: 'action', itemId: 'start_game'); - context.bloc() - .add(StartGame()); + Analytics().logSelectContent( + contentType: 'action', itemId: 'start_game'); + context.read().add(StartGame()); }) : null, body: BlocListener( - bloc: context.bloc(), + bloc: context.read(), listener: (context, state) { if (state.error != null) { context.scaffold ..hideCurrentSnackBar() ..showSnackBar( SnackBar( - content: Text(state.error), + content: Text(state.error!), backgroundColor: AppColors.primary, ), ); @@ -138,22 +144,26 @@ class WaitingRoomScreen extends StatelessWidget { /// that needs to update to the change in game state Widget _buildPlayerList(BuildContext context, GameViewState state) { var players = state.players ?? []; - var hasRandoBeenInvitedOrNotOwner = (state.players?.any((element) => element.isRandoCardrissian) ?? false) || !state.isOurGame; + var hasRandoBeenInvitedOrNotOwner = + (state.players?.any((element) => element.isRandoCardrissian) ?? + false) || + !state.isOurGame; return ListView.builder( padding: const EdgeInsets.symmetric(vertical: 8), - itemCount: hasRandoBeenInvitedOrNotOwner ? players.length : players.length + 1, + itemCount: + hasRandoBeenInvitedOrNotOwner ? players.length : players.length + 1, itemBuilder: (context, index) { if (index < players.length) { var player = players[index]; return _buildPlayerTile(context, player, index); } else { - return _buildRandoCardrissianInvite(context, state.game.id); + return _buildRandoCardrissianInvite(context, state.game!.id!); } }); } Widget _buildPlayerTile(BuildContext context, Player player, int index) { - var playerName = player.name ?? Player.DEFAULT_NAME; + var playerName = player.name == "" ? Player.DEFAULT_NAME : ""; if (playerName.trim().isEmpty) { playerName = Player.DEFAULT_NAME; } @@ -162,18 +172,19 @@ class WaitingRoomScreen extends StatelessWidget { onTap: () {}, title: Text( playerName, - style: context.theme.textTheme.subtitle1.copyWith(color: Colors.white), + style: + context.theme.textTheme.subtitle1?.copyWith(color: Colors.white), ), - leading: PlayerCircleAvatar(player: player) - ); + leading: PlayerCircleAvatar(player: player)); } - Widget _buildRandoCardrissianInvite(BuildContext context, String gameDocumentId) { + Widget _buildRandoCardrissianInvite( + BuildContext context, String gameDocumentId) { return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 4), title: Text( "Invite Rando Cardrissian?", - style: context.theme.textTheme.subtitle1.copyWith( + style: context.theme.textTheme.subtitle1?.copyWith( color: Colors.white, fontWeight: FontWeight.bold, ), @@ -183,8 +194,10 @@ class WaitingRoomScreen extends StatelessWidget { ), trailing: Icon(MdiIcons.robot, color: Colors.white), onTap: () async { - Analytics().logSelectContent(contentType: 'players', itemId: 'invite_rando_cardrissian'); - await context.repository() + Analytics().logSelectContent( + contentType: 'players', itemId: 'invite_rando_cardrissian'); + await context + .read() .addRandoCardrissian(gameDocumentId); }, ); @@ -194,11 +207,9 @@ class WaitingRoomScreen extends StatelessWidget { if (kIsWeb) { // await shareWeb(link); await FlutterClipboard.copy(link); - context.scaffold.showSnackBar( - SnackBar( - content: Text("Link copied to clipboard!"), - ) - ); + context.scaffold.showSnackBar(SnackBar( + content: Text("Link copied to clipboard!"), + )); } else { await Share.share(link.toString()); } @@ -214,5 +225,4 @@ class WaitingRoomScreen extends StatelessWidget { // link.href = linkToShare; // link.click(); } - } diff --git a/lib/ui/home/bloc/home_bloc.dart b/lib/ui/home/bloc/home_bloc.dart index 0df8d35..c6632e5 100644 --- a/lib/ui/home/bloc/home_bloc.dart +++ b/lib/ui/home/bloc/home_bloc.dart @@ -8,12 +8,26 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:logging/logging.dart'; class HomeBloc extends Bloc { - UserRepository _userRepository; - GameRepository _gameRepository; - StreamSubscription _userSubscription; - StreamSubscription _joinedGamesSubscription; + UserRepository? _userRepository; + GameRepository? _gameRepository; + StreamSubscription? _userSubscription; + StreamSubscription? _joinedGamesSubscription; - HomeBloc(this._userRepository, this._gameRepository); + HomeBloc({ + UserRepository? userRepository, + GameRepository? gameRepository, + StreamSubscription? userSubscription, + StreamSubscription? joinedGameSubscription, + }) : _userRepository = userRepository, + _gameRepository = gameRepository, + _userSubscription = userSubscription, + _joinedGamesSubscription = joinedGameSubscription, + super( + HomeState( + // user: User(), + isLoading: false, + ), + ) {} @override HomeState get initialState => HomeState.loading(); @@ -22,7 +36,7 @@ class HomeBloc extends Bloc { Stream mapEventToState(HomeEvent event) async* { if (event is HomeStarted) { yield* _mapHomeStartedToState(); - } else if (event is JoinedGamesUpdated){ + } else if (event is JoinedGamesUpdated) { yield* _mapJoinedGamesUpdatedToState(event); } else if (event is UserUpdated) { yield* _mapUserUpdatedToState(event); @@ -36,12 +50,13 @@ class HomeBloc extends Bloc { Stream _mapHomeStartedToState() async* { try { _userSubscription?.cancel(); - _userSubscription = _userRepository.observeUser().listen((user) { + _userSubscription = _userRepository?.observeUser().listen((user) { add(UserUpdated(user)); }); _joinedGamesSubscription?.cancel(); - _joinedGamesSubscription = _gameRepository.observeJoinedGames().listen((event) { + _joinedGamesSubscription = + _gameRepository?.observeJoinedGames().listen((event) { add(JoinedGamesUpdated(event)); }); } catch (e, stacktrace) { @@ -50,8 +65,10 @@ class HomeBloc extends Bloc { } } - Stream _mapJoinedGamesUpdatedToState(JoinedGamesUpdated event) async* { - yield state.copyWith(games: event.games..sort((a, b) => b.joinedAt.compareTo(a.joinedAt))); + Stream _mapJoinedGamesUpdatedToState( + JoinedGamesUpdated event) async* { + yield state.copyWith( + games: event.games..sort((a, b) => b.joinedAt.compareTo(a.joinedAt))); } Stream _mapUserUpdatedToState(UserUpdated event) async* { @@ -60,8 +77,9 @@ class HomeBloc extends Bloc { Stream _mapLeaveGameToState(LeaveGame event) async* { try { - yield state.copyWith(leavingGame: event.game, games: state.games..remove(event.game)); - await _gameRepository.leaveGame(event.game); + yield state.copyWith( + leavingGame: event.game, games: state.games?..remove(event.game)); + await _gameRepository?.leaveGame(event.game); yield state.copyWith(leavingGame: null); } catch (e) { yield state.copyWith(leavingGame: null, error: "$e"); @@ -71,10 +89,12 @@ class HomeBloc extends Bloc { Stream _mapJoinGameToState(JoinGame event) async* { try { yield state.copyWith(joiningGame: event.gameCode); - var game = await _gameRepository.joinGame(event.gameCode); - yield state.copyWith(joiningGame: null, joinedGame: game, overrideNull: true); + var game = await _gameRepository?.joinGame(event.gameCode); + yield state.copyWith( + joiningGame: null, joinedGame: game, overrideNull: true); } catch (e) { - yield state.copyWith(joiningGame: null, joinedGame: null, error: "$e", overrideNull: true); + yield state.copyWith( + joiningGame: null, joinedGame: null, error: "$e", overrideNull: true); } } } diff --git a/lib/ui/home/bloc/home_state.dart b/lib/ui/home/bloc/home_state.dart index 43a2eb1..c5385b4 100644 --- a/lib/ui/home/bloc/home_state.dart +++ b/lib/ui/home/bloc/home_state.dart @@ -5,42 +5,46 @@ import 'package:meta/meta.dart'; @immutable class HomeState { - final User user; - final List games; - final UserGame leavingGame; - final String joiningGame; - final Game joinedGame; - final String error; + final User? user; + final List? games; + final UserGame? leavingGame; + final String? joiningGame; + final Game? joinedGame; + final String? error; final bool isLoading; HomeState({ - @required this.user, - @required this.isLoading, - List games, + this.user, + List? games, this.leavingGame, this.joiningGame, this.joinedGame, this.error, - }) : games = games ?? []; + required this.isLoading, + }) : games = games; factory HomeState.loading() { return HomeState( - user: null, + // user: User( + // id: '', + // name: '', + // avatarUrl: '', + // updatedAt: DateTime.now(), + // ), isLoading: true, error: null, ); } - HomeState copyWith({ - User user, - List games, - UserGame leavingGame, - String joiningGame, - Game joinedGame, - bool isLoading, - String error, - bool overrideNull = false - }) { + HomeState copyWith( + {User? user, + List? games, + UserGame? leavingGame, + String? joiningGame, + Game? joinedGame, + bool? isLoading, + String? error, + bool overrideNull = false}) { return HomeState( user: user ?? this.user, games: games ?? this.games, diff --git a/lib/ui/home/home_screen_v2.dart b/lib/ui/home/home_screen_v2.dart index 67588e8..8677257 100644 --- a/lib/ui/home/home_screen_v2.dart +++ b/lib/ui/home/home_screen_v2.dart @@ -1,5 +1,6 @@ -import 'dart:io'; +// import 'dart:io'; +import 'package:appsagainsthumanity/data/features/users/user_repository.dart'; import 'package:appsagainsthumanity/internal/push.dart'; import 'package:appsagainsthumanity/ui/creategame/create_game_screen.dart'; import 'package:appsagainsthumanity/ui/home/bloc/bloc.dart'; @@ -25,21 +26,22 @@ class _HomeScreenV2State extends State { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => HomeBloc(context.repository(), context.repository())..add(HomeStarted()), + create: (context) => HomeBloc()..add(HomeStarted()), child: MultiBlocListener( listeners: [ // Error Listener BlocListener( - condition: (previous, current) => previous.error != current.error && current.error != null, + listenWhen: (previous, current) => + previous.error != current.error && current.error != "", listener: (context, state) { - if (state.error != null) { - Scaffold.of(context) + if (state.error != "") { + ScaffoldMessenger.of(context) ..hideCurrentSnackBar() ..showSnackBar( SnackBar( content: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [Text(state.error), Icon(Icons.error)], + children: [Text(state.error!), Icon(Icons.error)], ), backgroundColor: Colors.redAccent, ), @@ -50,10 +52,11 @@ class _HomeScreenV2State extends State { // Joined Game listener that opens a 'joined' game BlocListener( - condition: (previous, current) => previous.joinedGame?.id != current.joinedGame?.id, + listenWhen: (previous, current) => + previous.joinedGame?.id != current.joinedGame?.id, listener: (context, state) { - if (state.joinedGame != null) { - Navigator.of(context).push(GamePageRoute(state.joinedGame)); + if (state.joinedGame != {}) { + Navigator.of(context).push(GamePageRoute(state.joinedGame!)); } }, ) @@ -76,21 +79,19 @@ class _HomeScreenV2State extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Title Container( margin: const EdgeInsets.only(left: 24, right: 24), child: Text( context.strings.appNameDisplay, style: GoogleFonts.raleway( - textStyle: context.theme.textTheme.headline3.copyWith( + textStyle: context.theme.textTheme.headline3?.copyWith( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 48, )), ), ), - Container( margin: const EdgeInsets.only(left: 24, right: 24, top: 32), child: Wrap( @@ -103,8 +104,10 @@ class _HomeScreenV2State extends State { UserWidget( state: state, onTap: () { - Analytics().logSelectContent(contentType: 'action', itemId: 'profile'); - Navigator.of(context).push(MaterialPageRoute(builder: (_) => ProfileScreen())); + Analytics().logSelectContent( + contentType: 'action', itemId: 'profile'); + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => ProfileScreen())); }, ), HomeOutlineButton( @@ -113,23 +116,28 @@ class _HomeScreenV2State extends State { color: AppColors.primaryVariant, ), text: "New Game", - onTap: state.joiningGame == null ? () { - Analytics().logSelectContent(contentType: 'action', itemId: 'start_game'); - PushNotifications().checkPermissions(); - Navigator.of(context).push(MaterialPageRoute(builder: (context) => CreateGameScreen())); - } : null, + onTap: state.joiningGame == "" + ? () { + Analytics().logSelectContent( + contentType: 'action', + itemId: 'start_game'); + PushNotifications().checkPermissions(); + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => CreateGameScreen())); + } + : () {}, ), JoinGameWidget(state), ], ), ), - if (state.games.isNotEmpty) + if (state.games!.isNotEmpty) Container( margin: const EdgeInsets.only(left: 24, top: 24, right: 24), child: Text( "Past Games", - style: context.theme.textTheme.headline4.copyWith( + style: context.theme.textTheme.headline4?.copyWith( color: Colors.white70, fontWeight: FontWeight.w500, fontSize: 24, @@ -137,7 +145,7 @@ class _HomeScreenV2State extends State { ), ), - if (state.games.isNotEmpty) + if (state.games!.isNotEmpty) Container( margin: const EdgeInsets.only(top: 16), child: Divider( @@ -146,17 +154,18 @@ class _HomeScreenV2State extends State { ), ), - if (state.games.isNotEmpty) + if (state.games!.isNotEmpty) Expanded( child: ListView.builder( - padding: const EdgeInsets.symmetric(vertical: 8), - itemCount: state.games.length, - itemBuilder: (context, index) { - var game = state.games[index]; - var isLeavingGame = game.id == state.leavingGame?.id; - return PastGame(game, isLeavingGame); - }), - ) + padding: const EdgeInsets.symmetric(vertical: 8), + itemCount: state.games!.length, + itemBuilder: (context, index) { + var game = state.games?[index]; + var isLeavingGame = game?.id == state.leavingGame?.id; + return PastGame(game!, isLeavingGame); + }, + ), + ), ], ), ), diff --git a/lib/ui/home/widgets/home_outline_button.dart b/lib/ui/home/widgets/home_outline_button.dart index 3451c96..101a3a8 100644 --- a/lib/ui/home/widgets/home_outline_button.dart +++ b/lib/ui/home/widgets/home_outline_button.dart @@ -1,4 +1,4 @@ -import 'package:appsagainsthumanity/app.dart'; +// import 'package:appsagainsthumanity/app.dart'; import 'package:flutter/material.dart'; import 'package:appsagainsthumanity/internal.dart'; @@ -7,13 +7,13 @@ class HomeOutlineButton extends StatelessWidget { final String text; final Color textColor; final Color borderColor; - final VoidCallback onTap; + final VoidCallback? onTap; HomeOutlineButton({ - @required this.icon, - @required this.text, + required this.icon, + required this.text, this.textColor = AppColors.primaryVariant, - this.borderColor = AppColors.primaryVariant, + this.borderColor = AppColors.primaryVariant, this.onTap, }); @@ -42,7 +42,7 @@ class HomeOutlineButton extends StatelessWidget { margin: const EdgeInsets.only(left: 8, right: 24), child: Text( text, - style: context.theme.textTheme.subtitle2.copyWith( + style: context.theme.textTheme.subtitle2?.copyWith( color: textColor, ), ), diff --git a/lib/ui/home/widgets/join_game_widget.dart b/lib/ui/home/widgets/join_game_widget.dart index a9fa2d9..a6f3c2d 100644 --- a/lib/ui/home/widgets/join_game_widget.dart +++ b/lib/ui/home/widgets/join_game_widget.dart @@ -15,10 +15,11 @@ class JoinGameWidget extends StatelessWidget { @override Widget build(BuildContext context) { return HomeOutlineButton( - icon: state.joiningGame == null ? _buildIcon() : _buildLoadingIcon(), - text: state.joiningGame == null ? "Join Game" : "Joining Game...", + icon: state.joiningGame == "" ? _buildIcon() : _buildLoadingIcon(), + text: state.joiningGame == "" ? "Join Game" : "Joining Game...", onTap: () { - Analytics().logSelectContent(contentType: 'action', itemId: 'join_game'); + Analytics() + .logSelectContent(contentType: 'action', itemId: 'join_game'); PushNotifications().checkPermissions(); _joinGame(context); }, @@ -36,14 +37,14 @@ class JoinGameWidget extends StatelessWidget { Widget _buildIcon() { return Icon( MdiIcons.gamepadVariantOutline, - color: AppColors.primaryVariant, + color: AppColors.primaryVariant, ); } void _joinGame(BuildContext context) async { var gameId = await showJoinRoomDialog(context); if (gameId != null) { - context.bloc().add(JoinGame(gameId)); + context.read().add(JoinGame(gameId)); } } } diff --git a/lib/ui/home/widgets/join_room_dialog.dart b/lib/ui/home/widgets/join_room_dialog.dart index e0d11b4..7f95162 100644 --- a/lib/ui/home/widgets/join_room_dialog.dart +++ b/lib/ui/home/widgets/join_room_dialog.dart @@ -1,8 +1,9 @@ -import 'dart:io'; +// import 'dart:io'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:appsagainsthumanity/data/firestore.dart'; import 'package:appsagainsthumanity/internal.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; class JoinRoomDialog extends StatefulWidget { @override @@ -25,18 +26,17 @@ class _JoinRoomDialogState extends State { mainAxisSize: MainAxisSize.min, children: [ Container( - margin: const EdgeInsets.only(left: 24, right: 24, top: 24), + margin: const EdgeInsets.only(left: 24, right: 24, top: 24), child: Form( key: _formKey, child: TextFormField( controller: _gameInputController, decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: 'Game ID', - labelStyle: context.theme.textTheme.caption - ), + border: OutlineInputBorder(), + labelText: 'Game ID', + labelStyle: context.theme.textTheme.caption), maxLength: FirebaseConstants.MAX_GID_SIZE, - maxLengthEnforced: true, + maxLengthEnforcement: MaxLengthEnforcement.enforced, keyboardType: TextInputType.text, autofocus: true, textCapitalization: TextCapitalization.characters, @@ -45,7 +45,7 @@ class _JoinRoomDialogState extends State { Navigator.of(context).pop(value); }, validator: (value) { - if (value.length != FirebaseConstants.MAX_GID_SIZE) { + if (value?.length != FirebaseConstants.MAX_GID_SIZE) { return 'Please enter a valid game id'; } return null; @@ -54,22 +54,30 @@ class _JoinRoomDialogState extends State { ), ), Container( - margin: const EdgeInsets.only(bottom: 8, top: 8), + margin: const EdgeInsets.only(bottom: 8, top: 8), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - FlatButton( - child: Text('CANCEL'), - textColor: AppColors.primary, + TextButton( + child: Text( + 'CANCEL', + style: TextStyle( + color: AppColors.primary, + ), + ), onPressed: () { Navigator.of(context).pop(); }, ), - FlatButton( - child: Text('JOIN'), - textColor: AppColors.primary, + TextButton( + child: Text( + 'JOIN', + style: TextStyle( + color: AppColors.primary, + ), + ), onPressed: () { - if (_formKey.currentState.validate()) { + if (_formKey.currentState!.validate()) { var gameId = _gameInputController.text; Navigator.of(context).pop(gameId); } @@ -83,34 +91,38 @@ class _JoinRoomDialogState extends State { } } -Future showJoinRoomDialog(BuildContext context) { +Future showJoinRoomDialog(BuildContext context) { if (kIsWeb) { return showGeneralDialog( - context: context, - pageBuilder: (context, _, __) { - return Theme( - data: AppThemes.dark, - child: AlertDialog( - title: Text('Join a game'), - content: JoinRoomDialog(), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - contentPadding: const EdgeInsets.all(0), - ), - ); - }); + context: context, + pageBuilder: (context, _, __) { + return Theme( + data: AppThemes.dark, + child: AlertDialog( + title: Text('Join a game'), + content: JoinRoomDialog(), + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + contentPadding: const EdgeInsets.all(0), + ), + ); + }, + ); } else { return showDialog( - context: context, - builder: (builderContext) { - return Theme( - data: AppThemes.dark, - child: AlertDialog( - title: Text('Join a game'), - content: JoinRoomDialog(), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - contentPadding: const EdgeInsets.all(0), - ), - ); - }); + context: context, + builder: (builderContext) { + return Theme( + data: AppThemes.dark, + child: AlertDialog( + title: Text('Join a game'), + content: JoinRoomDialog(), + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + contentPadding: const EdgeInsets.all(0), + ), + ); + }, + ); } } diff --git a/lib/ui/home/widgets/past_game.dart b/lib/ui/home/widgets/past_game.dart index cc279ef..9fbe05a 100644 --- a/lib/ui/home/widgets/past_game.dart +++ b/lib/ui/home/widgets/past_game.dart @@ -10,116 +10,129 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart import 'package:timeago/timeago.dart' as timeago; class PastGame extends StatelessWidget { - final UserGame game; - final bool isLeavingGame; + final UserGame game; + final bool isLeavingGame; - PastGame(this.game, this.isLeavingGame); + PastGame(this.game, this.isLeavingGame); - @override - Widget build(BuildContext context) { - return Dismissible( - key: Key(game.id), - child: _buildListTile(context), - direction: DismissDirection.horizontal, - background: _buildBackground(context), - confirmDismiss: (_) => confirmDismiss(context), - onDismissed: (direction) { - Analytics().logSelectContent(contentType: 'past_game', itemId: 'leave'); - context.bloc().add(LeaveGame(game)); - }, - ); - } + @override + Widget build(BuildContext context) { + return Dismissible( + key: Key(game.id!), + child: _buildListTile(context), + direction: DismissDirection.horizontal, + background: _buildBackground(context), + confirmDismiss: (_) => confirmDismiss(context), + onDismissed: (direction) { + Analytics().logSelectContent(contentType: 'past_game', itemId: 'leave'); + context.read().add(LeaveGame(game)); + }, + ); + } - Widget _buildListTile(BuildContext context) { - return ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 24), - title: Text( - game.gid, - style: context.theme.textTheme.subtitle1.copyWith( - color: AppColors.primaryVariant, - fontWeight: FontWeight.w500, - ), + Widget _buildListTile(BuildContext context) { + return ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + title: Text( + game.gid, + style: context.theme.textTheme.subtitle1?.copyWith( + color: AppColors.primaryVariant, + fontWeight: FontWeight.w500, + ), + ), + subtitle: Text( + isLeavingGame ? "Leaving..." : game.state.label, + style: context.theme.textTheme.bodyText2?.copyWith( + color: Colors.white60, + ), + ), + trailing: isLeavingGame + ? Container(width: 24, height: 24, child: CircularProgressIndicator()) + : Text( + timeago.format(game.joinedAt), + style: context.theme.textTheme.bodyText2?.copyWith( + color: Colors.white54, + ), ), - subtitle: Text( - isLeavingGame ? "Leaving..." : game.state.label, - style: context.theme.textTheme.bodyText2.copyWith( - color: Colors.white60, - ), + onTap: game.state == GameState.inProgress || + game.state == GameState.waitingRoom + ? () => openGame(context) + : null, + ); + } + + Widget _buildBackground(BuildContext context) { + return Container( + color: Colors.redAccent[200], + child: Row( + children: [ + Container( + margin: const EdgeInsets.only(left: 16), + child: Icon( + MdiIcons.deleteEmpty, + color: Colors.white, ), - trailing: isLeavingGame - ? Container(width: 24, height: 24, child: CircularProgressIndicator()) - : Text( - timeago.format(game.joinedAt), - style: context.theme.textTheme.bodyText2.copyWith( - color: Colors.white54, - ), + ), + Expanded( + child: Container(), + ), + Container( + margin: const EdgeInsets.only(right: 16), + child: Icon( + MdiIcons.deleteEmpty, + color: Colors.white, ), - onTap: game.state == GameState.inProgress || game.state == GameState.waitingRoom ? () => openGame(context) : null, - ); - } - - Widget _buildBackground(BuildContext context) { - return Container( - color: Colors.redAccent[200], - child: Row( - children: [ - Container( - margin: const EdgeInsets.only(left: 16), - child: Icon( - MdiIcons.deleteEmpty, - color: Colors.white, - ), - ), - Expanded( - child: Container(), - ), - Container( - margin: const EdgeInsets.only(right: 16), - child: Icon( - MdiIcons.deleteEmpty, - color: Colors.white, - ), - ), - ], ), - ); - } + ], + ), + ); + } - void openGame(BuildContext context) async { - Analytics().logSelectContent(contentType: 'past_game', itemId: 'open'); - try { - var existingGame = await context.repository().getGame(game.id); - Navigator.of(context).push(GamePageRoute(existingGame)); - } catch (e) { - Scaffold.of(context) - ..hideCurrentSnackBar() - ..showSnackBar(SnackBar( - content: Text("$e"), - )); - } + void openGame(BuildContext context) async { + Analytics().logSelectContent(contentType: 'past_game', itemId: 'open'); + try { + var existingGame = await context.read().getGame(game.id!); + Navigator.of(context).push(GamePageRoute(existingGame)); + } catch (e) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar(SnackBar( + content: Text("$e"), + )); } + } - Future confirmDismiss(BuildContext context) { - return showDialog( - context: context, - builder: (context) { - return AlertDialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - title: Text("Leave game?"), - content: Text("Are you sure you want to leave the game ${game.gid}?"), - actions: [ - FlatButton( - child: Text("CANCEL"), - textColor: AppColors.primaryVariant, - onPressed: () => Navigator.of(context).pop(false), - ), - FlatButton( - child: Text("LEAVE"), - textColor: AppColors.primaryVariant, - onPressed: () => Navigator.of(context).pop(true), - ), - ], - ); - }); - } + Future confirmDismiss(BuildContext context) { + return showDialog( + context: context, + builder: (context) { + return AlertDialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + title: Text("Leave game?"), + content: Text("Are you sure you want to leave the game ${game.gid}?"), + actions: [ + TextButton( + child: Text( + "CANCEL", + style: TextStyle( + color: AppColors.primaryVariant, + ), + ), + onPressed: () => Navigator.of(context).pop(false), + ), + TextButton( + child: Text( + "LEAVE", + style: TextStyle( + color: AppColors.primaryVariant, + ), + ), + onPressed: () => Navigator.of(context).pop(true), + ), + ], + ); + }, + ); + } } diff --git a/lib/ui/home/widgets/user_widget.dart b/lib/ui/home/widgets/user_widget.dart index 91b3dd4..333219c 100644 --- a/lib/ui/home/widgets/user_widget.dart +++ b/lib/ui/home/widgets/user_widget.dart @@ -6,21 +6,29 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart class UserWidget extends StatelessWidget { final HomeState state; - final VoidCallback onTap; + final VoidCallback? onTap; UserWidget({ - @required this.state, + required this.state, this.onTap, }); @override Widget build(BuildContext context) { return HomeOutlineButton( - icon: state.isLoading ? _buildLoadingIcon() : state.error != null ? _buildErrorIcon() : _buildUserIcon(), - text: state.isLoading ? "Loading..." : state.error != null ? "Uh oh!" : state.user.name, + icon: state.isLoading + ? _buildLoadingIcon() + : state.error != "" + ? _buildErrorIcon() + : _buildUserIcon(), + text: state.isLoading + ? "Loading..." + : state.error != "" + ? "Uh oh!" + : state.user!.name, textColor: Colors.white, // borderColor: Colors.white, - onTap: onTap, + onTap: onTap!, ); } @@ -54,7 +62,9 @@ class UserWidget extends StatelessWidget { height: 24, child: CircleAvatar( radius: 20, - backgroundImage: state.user.avatarUrl != null ? NetworkImage(state.user.avatarUrl) : null, + backgroundImage: state.user!.avatarUrl != "" + ? NetworkImage(state.user!.avatarUrl) + : NetworkImage(""), backgroundColor: AppColors.primary, ), ); diff --git a/lib/ui/profile/bloc/profile_bloc.dart b/lib/ui/profile/bloc/profile_bloc.dart index d5fc2e7..0b1c78f 100644 --- a/lib/ui/profile/bloc/profile_bloc.dart +++ b/lib/ui/profile/bloc/profile_bloc.dart @@ -6,19 +6,23 @@ import 'package:appsagainsthumanity/ui/profile/bloc/profile_state.dart'; import 'package:bloc/bloc.dart'; class ProfileBloc extends Bloc { - final UserRepository _userRepository; + final UserRepository? _userRepository; + StreamSubscription? _userSubscription; - StreamSubscription _userSubscription; - - ProfileBloc(this._userRepository); + ProfileBloc({ + UserRepository? userRepository, + StreamSubscription? userSubscription, + }) : _userRepository = userRepository, + _userSubscription = userSubscription, + super(ProfileState()) {} @override ProfileState get initialState => ProfileState(); @override Future close() async { - super.close(); - await _userSubscription?.cancel(); + super.close(); + await _userSubscription?.cancel(); } @override @@ -28,53 +32,54 @@ class ProfileBloc extends Bloc { } else if (event is UserLoaded) { yield* _mapUserLoadedToState(event); } else if (event is DisplayNameChanged) { - yield* _mapDisplayNameChangedToState(event); + yield* _mapDisplayNameChangedToState(event); } else if (event is PhotoChanged) { - yield* _mapPhotoChangedToState(event); + yield* _mapPhotoChangedToState(event); } else if (event is DeleteProfilePhoto) { - yield* _mapDeletePhotoToState(); + yield* _mapDeletePhotoToState(); } } Stream _mapScreenLoadedToState() async* { - _userSubscription?.cancel(); - _userSubscription = _userRepository.observeUser().listen((user) { - add(UserLoaded(user)); - }); + _userSubscription?.cancel(); + _userSubscription = _userRepository?.observeUser().listen((user) { + add(UserLoaded(user)); + }); } Stream _mapUserLoadedToState(UserLoaded event) async* { yield state.copyWith(user: event.user, isLoading: false, error: null); } - Stream _mapDisplayNameChangedToState(DisplayNameChanged event) async* { - yield state.copyWith(isLoading: true, error: null); - try { - await _userRepository.updateDisplayName(event.name); - yield state.copyWith(isLoading: false); - } catch (e) { - yield state.copyWith(isLoading: false, error: e.toString()); - } + Stream _mapDisplayNameChangedToState( + DisplayNameChanged event) async* { + yield state.copyWith(isLoading: true, error: null); + try { + await _userRepository?.updateDisplayName(event.name); + yield state.copyWith(isLoading: false); + } catch (e) { + yield state.copyWith(isLoading: false, error: e.toString()); + } } Stream _mapPhotoChangedToState(PhotoChanged event) async* { - yield state.copyWith(isLoading: true, error: null); - try { - final imageBytes = await event.file.readAsBytes(); - await _userRepository.updateProfilePhoto(imageBytes); - yield state.copyWith(isLoading: false); - } catch (e) { - yield state.copyWith(isLoading: false, error: e.toString()); - } + yield state.copyWith(isLoading: true, error: null); + try { + final imageBytes = await event.file.readAsBytes(); + await _userRepository?.updateProfilePhoto(imageBytes); + yield state.copyWith(isLoading: false); + } catch (e) { + yield state.copyWith(isLoading: false, error: e.toString()); + } } - + Stream _mapDeletePhotoToState() async* { - yield state.copyWith(isLoading: true, error: null); - try { - await _userRepository.deleteProfilePhoto(); - yield state.copyWith(isLoading: false); - } catch (e) { - yield state.copyWith(isLoading: false, error: e.toString()); - } + yield state.copyWith(isLoading: true, error: null); + try { + await _userRepository?.deleteProfilePhoto(); + yield state.copyWith(isLoading: false); + } catch (e) { + yield state.copyWith(isLoading: false, error: e.toString()); + } } } diff --git a/lib/ui/profile/bloc/profile_event.dart b/lib/ui/profile/bloc/profile_event.dart index 1fa311c..95ced9c 100644 --- a/lib/ui/profile/bloc/profile_event.dart +++ b/lib/ui/profile/bloc/profile_event.dart @@ -1,4 +1,4 @@ -import 'dart:io'; +// import 'dart:io'; import 'package:appsagainsthumanity/data/features/users/model/user.dart'; import 'package:equatable/equatable.dart'; @@ -36,7 +36,7 @@ class DisplayNameChanged extends ProfileEvent { @immutable class PhotoChanged extends ProfileEvent { - final PickedFile file; + final XFile file; PhotoChanged(this.file); diff --git a/lib/ui/profile/bloc/profile_state.dart b/lib/ui/profile/bloc/profile_state.dart index 2765e3c..e2a5c97 100644 --- a/lib/ui/profile/bloc/profile_state.dart +++ b/lib/ui/profile/bloc/profile_state.dart @@ -4,8 +4,8 @@ import 'package:meta/meta.dart'; @immutable class ProfileState { final bool isLoading; - final String error; - final User user; + final String? error; + final User? user; ProfileState({ this.isLoading = true, @@ -14,9 +14,9 @@ class ProfileState { }); ProfileState copyWith({ - bool isLoading, - String error, - User user, + bool? isLoading, + String? error, + User? user, }) { return ProfileState( isLoading: isLoading ?? this.isLoading, diff --git a/lib/ui/profile/profile_screen.dart b/lib/ui/profile/profile_screen.dart index 1421c16..5fb3ab7 100644 --- a/lib/ui/profile/profile_screen.dart +++ b/lib/ui/profile/profile_screen.dart @@ -1,7 +1,9 @@ +import 'package:appsagainsthumanity/data/features/users/user_repository.dart'; import 'package:appsagainsthumanity/internal.dart'; import 'package:appsagainsthumanity/ui/profile/bloc/bloc.dart'; import 'package:appsagainsthumanity/ui/profile/widgets/profile_photo.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; @@ -22,22 +24,26 @@ class _ProfileScreenState extends State { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => ProfileBloc(context.repository())..add(ScreenLoaded()), + create: (context) => ProfileBloc()..add(ScreenLoaded()), child: _buildBody(), ); } Widget _buildBody() { return BlocConsumer( - listenWhen: (previous, current) => current.user?.name != previous.user?.name, + listenWhen: (previous, current) => + current.user?.name != previous.user?.name, listener: (context, state) { - _displayNameController.text = state.user?.name; + _displayNameController.text = state.user!.name; }, builder: (context, state) { return Scaffold( appBar: AppBar( - brightness: Brightness.dark, - textTheme: context.theme.textTheme, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarBrightness: Brightness.dark, + ), + // brightness: Brightness.dark, + // textTheme: context.theme.textTheme, iconTheme: context.theme.iconTheme, title: Text("Profile"), backgroundColor: AppColors.surfaceDark, @@ -50,24 +56,27 @@ class _ProfileScreenState extends State { alignment: Alignment.center, children: [ ProfilePhoto( - state.user, + state.user!, size: 128, ), if (state.isLoading) CircularProgressIndicator(), - if (!state.isLoading && state.user?.avatarUrl != null) _buildEditPhotoStack(context) + if (!state.isLoading && state.user?.avatarUrl != "") + _buildEditPhotoStack(context) ], ), ), Container( - margin: const EdgeInsets.only(left: 16, right: 16, bottom: 16, top: 16), + margin: const EdgeInsets.only( + left: 16, right: 16, bottom: 16, top: 16), child: ListTile( title: Text( "User ID", - style: context.theme.textTheme.subtitle1.copyWith( + style: context.theme.textTheme.subtitle1?.copyWith( color: AppColors.primaryVariant, ), ), - subtitle: Text(state.user?.id ?? "Loading..."), + subtitle: + Text(state.user?.id == "" ? "Loading..." : "No User"), ), ), Container( @@ -81,7 +90,7 @@ class _ProfileScreenState extends State { keyboardType: TextInputType.text, textInputAction: TextInputAction.go, onFieldSubmitted: (value) { - context.bloc().add(DisplayNameChanged(value)); + context.read().add(DisplayNameChanged(value)); }, ), ) @@ -109,7 +118,7 @@ class _ProfileScreenState extends State { margin: const EdgeInsets.only(left: 8), child: Text( "EDIT", - style: context.theme.textTheme.subtitle1.copyWith( + style: context.theme.textTheme.subtitle1?.copyWith( fontWeight: FontWeight.w700, color: AppColors.primaryVariant, ), @@ -122,9 +131,9 @@ class _ProfileScreenState extends State { } String getUserInitials(String name) { - if (name != null) { + if (name != "") { var splitName = name.split(' '); - if (splitName != null && splitName.isNotEmpty) { + if (splitName != "" && splitName.isNotEmpty) { var nonEmptyCharacters = splitName.where((e) => e.isNotEmpty); if (nonEmptyCharacters.isNotEmpty) { return nonEmptyCharacters.map((e) => e[0]).join().toUpperCase(); diff --git a/lib/ui/profile/widgets/profile_photo.dart b/lib/ui/profile/widgets/profile_photo.dart index 8f49a7f..eba0339 100644 --- a/lib/ui/profile/widgets/profile_photo.dart +++ b/lib/ui/profile/widgets/profile_photo.dart @@ -1,4 +1,4 @@ -import 'dart:io'; +// import 'dart:io'; import 'package:appsagainsthumanity/data/features/users/model/user.dart'; import 'package:appsagainsthumanity/internal.dart'; @@ -15,8 +15,8 @@ class ProfilePhoto extends StatelessWidget { @override Widget build(BuildContext context) { - var profilePhotoUrl = user?.avatarUrl; - return profilePhotoUrl != null + var profilePhotoUrl = user.avatarUrl; + return profilePhotoUrl != "" ? _buildExistingPhotoAction(context, profilePhotoUrl) : _buildAddPhotoAction(context); } @@ -43,12 +43,14 @@ class ProfilePhoto extends StatelessWidget { size: 46.0, color: AppColors.addPhotoForeground, ), - Container( margin: const EdgeInsets.only(top: 4), child: Text( "Add photo", - style: Theme.of(context).textTheme.button.copyWith(color: AppColors.addPhotoForeground), + style: Theme.of(context) + .textTheme + .button + ?.copyWith(color: AppColors.addPhotoForeground), ), ) ], @@ -81,27 +83,25 @@ class ProfilePhoto extends StatelessWidget { var action = await _showPhotoBottomSheet(context); print("Result: $action"); if (action is DeletePhoto) { - context.bloc() - .add(DeleteProfilePhoto()); + context.read().add(DeleteProfilePhoto()); } else if (action is UpdatePhoto) { - var image = await ImagePicker().getImage(source: action.source); + var image = await ImagePicker().pickImage(source: action.source); if (image != null) { print("Photo selected: ${image.path}"); - context.bloc() - .add(PhotoChanged(image)); + context.read().add(PhotoChanged(image)); } } } - Future _showPhotoBottomSheet(BuildContext context) async { + Future _showPhotoBottomSheet( + BuildContext context) async { return await showModalBottomSheet( context: context, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ) - ), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + )), backgroundColor: context.theme.canvasColor, builder: (context) { return Container( @@ -111,7 +111,8 @@ class ProfilePhoto extends StatelessWidget { padding: EdgeInsets.all(16), child: Text( "Update your profile photo", - style: context.theme.textTheme.bodyText1.copyWith(color: Colors.white70), + style: context.theme.textTheme.bodyText1 + ?.copyWith(color: Colors.white70), ), ), ListTile( diff --git a/lib/ui/routes.dart b/lib/ui/routes.dart index fac4b5a..e1f0ae2 100644 --- a/lib/ui/routes.dart +++ b/lib/ui/routes.dart @@ -25,39 +25,39 @@ class GamePageRoute extends MaterialPageRoute { } class NoAnimationPageRoute extends MaterialPageRoute { - NoAnimationPageRoute({WidgetBuilder builder}) : super(builder: builder); + NoAnimationPageRoute({WidgetBuilder? builder}) : super(builder: builder!); @override - Widget buildTransitions( - BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { + Widget buildTransitions(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { return child; } } class RouteTracer extends NavigatorObserver { - Route currentRoute; + Route? currentRoute; bool logEnabled = false; @override - void didPop(Route route, Route previousRoute) { + void didPop(Route route, Route? previousRoute) { currentRoute = previousRoute; if (logEnabled) print("Route Changed => $currentRoute"); } @override - void didReplace({Route newRoute, Route oldRoute}) { + void didReplace({Route? newRoute, Route? oldRoute}) { currentRoute = newRoute; if (logEnabled) print("Route Changed => $currentRoute"); } @override - void didRemove(Route route, Route previousRoute) { + void didRemove(Route route, Route? previousRoute) { currentRoute = previousRoute; if (logEnabled) print("Route Changed => $currentRoute"); } @override - void didPush(Route route, Route previousRoute) { + void didPush(Route route, Route? previousRoute) { currentRoute = route; if (logEnabled) print("Route Changed => $currentRoute"); } diff --git a/lib/ui/settings/settings_screen.dart b/lib/ui/settings/settings_screen.dart index 93d967e..f649f5a 100644 --- a/lib/ui/settings/settings_screen.dart +++ b/lib/ui/settings/settings_screen.dart @@ -30,11 +30,15 @@ class _SettingsScreenState extends State { appBar: AppBar( title: Text( "Settings", - style: context.theme.textTheme.headline6.copyWith(color: Colors.white), + style: + context.theme.textTheme.headline6?.copyWith(color: Colors.white), ), iconTheme: context.theme.iconTheme, backgroundColor: Colors.transparent, - brightness: Brightness.dark, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarBrightness: Brightness.dark, + ), + // brightness: Brightness.dark, elevation: 0, ), body: ListView( @@ -44,8 +48,10 @@ class _SettingsScreenState extends State { children: [ UserPreference( onTap: (user) { - Analytics().logSelectContent(contentType: 'setting', itemId: 'profile'); - Navigator.of(context).push(MaterialPageRoute(builder: (_) => ProfileScreen())); + Analytics().logSelectContent( + contentType: 'setting', itemId: 'profile'); + Navigator.of(context) + .push(MaterialPageRoute(builder: (_) => ProfileScreen())); }, ), Preference( @@ -82,7 +88,8 @@ class _SettingsScreenState extends State { color: context.secondaryColorOnCard, ), onTap: () { - Analytics().logSelectContent(contentType: 'setting', itemId: 'privacy_policy'); + Analytics().logSelectContent( + contentType: 'setting', itemId: 'privacy_policy'); showWebView( context, "Privacy Policy", @@ -97,7 +104,8 @@ class _SettingsScreenState extends State { color: context.secondaryColorOnCard, ), onTap: () { - Analytics().logSelectContent(contentType: 'setting', itemId: 'terms_of_service'); + Analytics().logSelectContent( + contentType: 'setting', itemId: 'terms_of_service'); showWebView( context, "Terms of service", @@ -112,7 +120,8 @@ class _SettingsScreenState extends State { color: context.secondaryColorOnCard, ), onTap: () { - Analytics().logSelectContent(contentType: 'setting', itemId: 'oss_licenses'); + Analytics().logSelectContent( + contentType: 'setting', itemId: 'oss_licenses'); showLicensePage(context: context); }, ), @@ -124,10 +133,12 @@ class _SettingsScreenState extends State { Preference( title: "Feedback", subtitle: "Provide feedback on issues or improvements", - icon: Icon(MdiIcons.faceAgent, color: context.secondaryColorOnCard), + icon: Icon(MdiIcons.faceAgent, + color: context.secondaryColorOnCard), onTap: () { - Analytics().logSelectContent(contentType: 'setting', itemId: 'feedback'); - Wiredash.of(context).show(); + Analytics().logSelectContent( + contentType: 'setting', itemId: 'feedback'); + Wiredash.of(context)?.show(); }, ), Preference( @@ -138,7 +149,8 @@ class _SettingsScreenState extends State { color: context.secondaryColorOnCard, ), onTap: () { - Analytics().logSelectContent(contentType: 'setting', itemId: 'contribute'); + Analytics().logSelectContent( + contentType: 'setting', itemId: 'contribute'); showWebView( context, "Contribute", @@ -153,7 +165,8 @@ class _SettingsScreenState extends State { color: context.secondaryColorOnCard, ), onTap: () { - Analytics().logSelectContent(contentType: 'setting', itemId: 'author'); + Analytics().logSelectContent( + contentType: 'setting', itemId: 'author'); showWebView( context, "52inc", @@ -169,18 +182,21 @@ class _SettingsScreenState extends State { threshold: 5, onTapCountReachedCallback: () { if (!AppPreferences().developerPackEnabled) { - Analytics().logSelectContent(contentType: 'setting', itemId: 'developer_packs'); + Analytics().logSelectContent( + contentType: 'setting', + itemId: 'developer_packs'); AppPreferences().developerPackEnabled = true; setState(() {}); - Scaffold.of(context).showSnackBar(SnackBar( + ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text("Developer Packs Unlocked!"), behavior: SnackBarBehavior.floating, action: SnackBarAction( label: "VIEW", textColor: context.primaryColor, onPressed: () { - Navigator.of(context) - .pushReplacement(MaterialPageRoute(builder: (_) => CreateGameScreen())); + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (_) => CreateGameScreen())); }, ), )); @@ -192,8 +208,9 @@ class _SettingsScreenState extends State { MdiIcons.application, color: context.secondaryColorOnCard, ), - subtitle: - packageInfo != null ? "${packageInfo.version}+${packageInfo.buildNumber}" : "Loading...", + subtitle: packageInfo != null + ? "${packageInfo.version}+${packageInfo.buildNumber}" + : "Loading...", ), ); }), @@ -221,11 +238,13 @@ class _SettingsScreenState extends State { trailing: AppPreferences().developerPackEnabled ? Text( "ENABLED", - style: context.theme.textTheme.button.copyWith(color: Colors.green), + style: context.theme.textTheme.button! + .copyWith(color: Colors.green), ) : Text( "DISABLED", - style: context.theme.textTheme.button.copyWith(color: Colors.redAccent), + style: context.theme.textTheme.button! + .copyWith(color: Colors.redAccent), ), icon: Image.asset( 'assets/ic_logo.png', @@ -241,13 +260,14 @@ class _SettingsScreenState extends State { children: [ GestureDetector( onTap: () { - showWebView(context, "Cards Against Humanity", "https://cardsagainsthumanity.com"); + showWebView(context, "Cards Against Humanity", + "https://cardsagainsthumanity.com"); }, child: Container( child: Text( "All CAH or \"Cards Against Humanity\" question and answer text are licensed under Creative Commons BY-NC-SA 4.0 by the owner Cards Against Humanity, LLC. This application is NOT official, produced, endorsed or supported by Cards Against Humanity, LLC.", textAlign: TextAlign.center, - style: context.theme.textTheme.bodyText2.copyWith( + style: context.theme.textTheme.bodyText2?.copyWith( color: Colors.white60, ), ), @@ -257,8 +277,8 @@ class _SettingsScreenState extends State { margin: const EdgeInsets.only(top: 16), child: GestureDetector( onTap: () { - showWebView( - context, "CC BY-NC-SA 4.0", "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode"); + showWebView(context, "CC BY-NC-SA 4.0", + "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode"); }, child: Image.asset( 'assets/cc_by_nc_sa.png', @@ -275,32 +295,42 @@ class _SettingsScreenState extends State { } void _deleteAccount(BuildContext context) async { - bool result = await showDialog( + bool? result = await showDialog( context: context, builder: (context) { return AlertDialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Text( "Delete account?", - style: context.theme.textTheme.headline6.copyWith(color: Colors.redAccent), + style: context.theme.textTheme.headline6! + .copyWith(color: Colors.redAccent), ), content: Text( "Are you sure you want to delete your account? This is permenant and cannot be undone.", - style: context.theme.textTheme.subtitle1.copyWith( + style: context.theme.textTheme.subtitle1!.copyWith( fontWeight: FontWeight.bold, ), ), actions: [ - FlatButton( - child: Text("CANCEL"), - textColor: Colors.white70, + TextButton( + child: Text( + "CANCEL", + style: TextStyle( + color: Colors.white70, + ), + ), onPressed: () { Navigator.of(context).pop(false); }, ), - FlatButton( - child: Text("DELETE ACCOUNT"), - textColor: Colors.redAccent, + TextButton( + child: Text( + "DELETE ACCOUNT", + style: TextStyle( + color: Colors.redAccent, + ), + ), onPressed: () { Navigator.of(context).pop(true); }, @@ -310,18 +340,19 @@ class _SettingsScreenState extends State { }); if (result ?? false) { - Analytics().logSelectContent(contentType: 'setting', itemId: 'delete_account'); - var userRepository = context.repository(); + Analytics() + .logSelectContent(contentType: 'setting', itemId: 'delete_account'); + var userRepository = context.read(); try { await userRepository.deleteAccount(); - context.bloc().add(LoggedOut()); + context.read().add(LoggedOut()); Navigator.of(context).pop(); } catch (e) { if (e is PlatformException) { if (e.code == 'ERROR_REQUIRES_RECENT_LOGIN') { await userRepository.signInWithGoogle(); await userRepository.deleteAccount(); - context.bloc().add(LoggedOut()); + context.read().add(LoggedOut()); Navigator.of(context).pop(); } } @@ -330,22 +361,22 @@ class _SettingsScreenState extends State { } void _signOut(BuildContext context) async { - var userRepository = context.repository(); + var userRepository = context.read(); await userRepository.signOut(); - context.bloc().add(LoggedOut()); + context.read().add(LoggedOut()); Navigator.of(context).pop(); } } class PreferenceCategory extends StatelessWidget { - final String title; + final String? title; final List children; - final EdgeInsets margin; + final EdgeInsets? margin; PreferenceCategory({ this.title, this.margin, - @required this.children, + required this.children, }); @override @@ -360,7 +391,8 @@ class PreferenceCategory extends StatelessWidget { padding: const EdgeInsets.only(bottom: 8), child: Column( children: [ - if (title != null) PreferenceHeader(title: title, includeIconSpacing: false), + if (title != null) + PreferenceHeader(title: title!, includeIconSpacing: false), ...children, ], ), diff --git a/lib/ui/settings/widgets/frequent_tap_detector.dart b/lib/ui/settings/widgets/frequent_tap_detector.dart index 93c07a2..93be93e 100644 --- a/lib/ui/settings/widgets/frequent_tap_detector.dart +++ b/lib/ui/settings/widgets/frequent_tap_detector.dart @@ -4,11 +4,11 @@ import 'package:flutter/material.dart'; class FrequentTapDetector extends StatefulWidget { final Widget child; - final int threshold; - final VoidCallback onTapCountReachedCallback; + final int? threshold; + final VoidCallback? onTapCountReachedCallback; FrequentTapDetector({ - @required this.child, + required this.child, this.threshold = 5, this.onTapCountReachedCallback, }); @@ -18,12 +18,12 @@ class FrequentTapDetector extends StatefulWidget { } class _FrequentTapDetectorState extends State { - Timer _timer; + late Timer _timer; int _tapCount = 0; @override void dispose() { - _timer?.cancel(); + _timer.cancel(); super.dispose(); } @@ -34,7 +34,7 @@ class _FrequentTapDetectorState extends State { onTap: () { _tapCount += 1; print("Tap Count ($_tapCount)"); - if (_tapCount >= widget.threshold) { + if (_tapCount >= widget.threshold!) { widget.onTapCountReachedCallback?.call(); _tapCount = 0; } else { @@ -45,7 +45,7 @@ class _FrequentTapDetectorState extends State { } void _startResetDelay() { - _timer?.cancel(); + _timer.cancel(); _timer = Timer(Duration(milliseconds: 1000), _onResetTapCount); } diff --git a/lib/ui/settings/widgets/preference.dart b/lib/ui/settings/widgets/preference.dart index 923d1a0..139bd3b 100644 --- a/lib/ui/settings/widgets/preference.dart +++ b/lib/ui/settings/widgets/preference.dart @@ -3,15 +3,15 @@ import 'package:appsagainsthumanity/internal.dart'; class Preference extends StatelessWidget { final String title; - final Color titleColor; - final FontWeight titleWeight; - final String subtitle; - final Widget icon; - final Widget trailing; - final VoidCallback onTap; + final Color? titleColor; + final FontWeight? titleWeight; + final String? subtitle; + final Widget? icon; + final Widget? trailing; + final VoidCallback? onTap; Preference({ - @required this.title, + required this.title, this.titleColor, this.titleWeight, this.subtitle, @@ -25,15 +25,14 @@ class Preference extends StatelessWidget { return ListTile( title: Text( title, - style: context.theme.textTheme.subtitle1.copyWith( - color: titleColor ?? context.colorOnCard, - fontWeight: titleWeight ?? FontWeight.normal - ), + style: context.theme.textTheme.subtitle1?.copyWith( + color: titleColor ?? context.colorOnCard, + fontWeight: titleWeight ?? FontWeight.normal), ), - subtitle: subtitle != null + subtitle: subtitle != "" ? Text( - subtitle, - style: context.theme.textTheme.bodyText1.copyWith( + subtitle!, + style: context.theme.textTheme.bodyText1?.copyWith( color: context.secondaryColorOnCard, ), ) diff --git a/lib/ui/settings/widgets/preference_header.dart b/lib/ui/settings/widgets/preference_header.dart index cd30b82..7a48dc5 100644 --- a/lib/ui/settings/widgets/preference_header.dart +++ b/lib/ui/settings/widgets/preference_header.dart @@ -5,7 +5,10 @@ class PreferenceHeader extends StatelessWidget { final String title; final bool includeIconSpacing; - PreferenceHeader({@required this.title, this.includeIconSpacing = true}); + PreferenceHeader({ + required this.title, + this.includeIconSpacing = true, + }); @override Widget build(BuildContext context) { @@ -16,7 +19,7 @@ class PreferenceHeader extends StatelessWidget { alignment: Alignment.centerLeft, child: Text( title, - style: context.theme.textTheme.subtitle2.copyWith( + style: context.theme.textTheme.subtitle2?.copyWith( color: context.primaryColor, ), ), diff --git a/lib/ui/settings/widgets/user_preference.dart b/lib/ui/settings/widgets/user_preference.dart index 805fe0c..745d05f 100644 --- a/lib/ui/settings/widgets/user_preference.dart +++ b/lib/ui/settings/widgets/user_preference.dart @@ -2,43 +2,51 @@ import 'package:appsagainsthumanity/data/features/users/model/user.dart'; import 'package:appsagainsthumanity/data/features/users/user_repository.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:kt_dart/kt.dart'; +// import 'package:kt_dart/kt.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:appsagainsthumanity/internal.dart'; class UserPreference extends StatelessWidget { final void Function(User) onTap; - UserPreference({this.onTap}); + UserPreference({required this.onTap}); @override Widget build(BuildContext context) { return StreamBuilder( - stream: context.repository().observeUser(), + stream: context.read().observeUser(), builder: (context, snapshot) { - return _buildPreference(context, snapshot?.data); + return _buildPreference(context, snapshot.data!); }, ); } - Widget _buildPreference(BuildContext context, @nullable User user) { + Widget _buildPreference(BuildContext context, User? user) { return ListTile( leading: _avatar(user), title: Text( - user?.name != null ? "${user.name}" : "Loading...", - style: context.theme.textTheme.subtitle1.copyWith( + user?.name != "" ? "${user?.name}" : "Loading...", + style: context.theme.textTheme.subtitle1?.copyWith( color: context.colorOnCard, ), ), - onTap: onTap != null ? () => onTap(user) : null, + onTap: onTap != () {} ? () => onTap(user!) : () {}, ); } - Widget _avatar(@nullable User user) { + Widget _avatar(User? user) { return CircleAvatar( - child: user?.avatarUrl != null ? null : Icon(MdiIcons.account, color: Colors.black87,), - backgroundColor: user?.avatarUrl != null ? Colors.black12 : AppColors.primary, - backgroundImage: user?.avatarUrl != null ? NetworkImage(user.avatarUrl) : null, + child: user?.avatarUrl != "" + ? const SizedBox() + : Icon( + MdiIcons.account, + color: Colors.black87, + ), + backgroundColor: + user?.avatarUrl != "" ? Colors.black12 : AppColors.primary, + backgroundImage: user?.avatarUrl != "" + ? NetworkImage(user!.avatarUrl) + : NetworkImage(""), radius: 20, ); } diff --git a/lib/ui/signin/bloc/sign_in_bloc.dart b/lib/ui/signin/bloc/sign_in_bloc.dart index 8f9396d..b08ddd2 100644 --- a/lib/ui/signin/bloc/sign_in_bloc.dart +++ b/lib/ui/signin/bloc/sign_in_bloc.dart @@ -5,14 +5,15 @@ import 'package:appsagainsthumanity/ui/signin/bloc/sign_in_event.dart'; import 'package:appsagainsthumanity/ui/signin/bloc/sign_in_state.dart'; import 'package:bloc/bloc.dart'; import 'package:logging/logging.dart'; -import 'package:meta/meta.dart'; +// import 'package:meta/meta.dart'; class SignInBloc extends Bloc { - UserRepository _userRepository; + final UserRepository _userRepository; - SignInBloc({@required UserRepository userRepository}) - : assert(userRepository != null), - _userRepository = userRepository; + SignInBloc({ + required UserRepository userRepository, + }) : _userRepository = userRepository, + super(SignInState.loading()); @override SignInState get initialState => SignInState.empty(); @@ -68,7 +69,8 @@ class SignInBloc extends Bloc { } } - Stream _mapLoginWithEmailPressedToState(LoginWithEmailPressed event) async* { + Stream _mapLoginWithEmailPressedToState( + LoginWithEmailPressed event) async* { yield SignInState.loading(); try { await _userRepository.signInWithEmail(event.email, event.password); @@ -80,10 +82,12 @@ class SignInBloc extends Bloc { } } - Stream _mapSignUpWithEmailPressedToState(SignUpWithEmailPressed event) async* { + Stream _mapSignUpWithEmailPressedToState( + SignUpWithEmailPressed event) async* { yield SignInState.loading(); try { - await _userRepository.signUpWithEmail(event.email, event.password, event.userName); + await _userRepository.signUpWithEmail( + event.email, event.password, event.userName); await Analytics().logSignUp(signUpMethod: "email"); yield SignInState.success(); } catch (e, stacktrace) { diff --git a/lib/ui/signin/bloc/sign_in_state.dart b/lib/ui/signin/bloc/sign_in_state.dart index a670a56..d7f729d 100644 --- a/lib/ui/signin/bloc/sign_in_state.dart +++ b/lib/ui/signin/bloc/sign_in_state.dart @@ -6,11 +6,10 @@ class SignInState { final bool isSuccess; final bool isFailure; - SignInState({ - @required this.isSubmitting, - @required this.isSuccess, - @required this.isFailure - }); + SignInState( + {required this.isSubmitting, + required this.isSuccess, + required this.isFailure}); factory SignInState.empty() { return SignInState( @@ -45,9 +44,9 @@ class SignInState { } SignInState copyWith({ - bool isSubmitting, - bool isSuccess, - bool isFailure, + bool? isSubmitting, + bool? isSuccess, + bool? isFailure, }) { return SignInState( isSubmitting: isSubmitting ?? this.isSubmitting, diff --git a/lib/ui/signin/layouts/mobile_layout.dart b/lib/ui/signin/layouts/mobile_layout.dart index 1f9f0bf..0a3f6cc 100644 --- a/lib/ui/signin/layouts/mobile_layout.dart +++ b/lib/ui/signin/layouts/mobile_layout.dart @@ -10,7 +10,6 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart import '../bloc/bloc.dart'; class MobileLayout extends StatelessWidget { - @override Widget build(BuildContext context) { return Column( @@ -21,7 +20,7 @@ class MobileLayout extends StatelessWidget { child: Text( context.strings.appNameDisplay, style: GoogleFonts.raleway( - textStyle: context.theme.textTheme.headline3.copyWith( + textStyle: context.theme.textTheme.headline3?.copyWith( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 48, @@ -65,7 +64,7 @@ class MobileLayout extends StatelessWidget { height: 24, ), onPressed: () { - context.bloc().add(LoginWithGooglePressed()); + context.read().add(LoginWithGooglePressed()); }, ), ), @@ -75,7 +74,7 @@ class MobileLayout extends StatelessWidget { text: context.strings.actionSignInAnonymously, iconData: MdiIcons.incognito, onPressed: () { - context.bloc().add(LoginAnonymouslyPressed()); + context.read().add(LoginAnonymouslyPressed()); }, ), ), diff --git a/lib/ui/signin/layouts/tablet_layout.dart b/lib/ui/signin/layouts/tablet_layout.dart index 10d2df2..670f0f9 100644 --- a/lib/ui/signin/layouts/tablet_layout.dart +++ b/lib/ui/signin/layouts/tablet_layout.dart @@ -20,7 +20,7 @@ class TabletLayout extends StatelessWidget { child: Text( context.strings.appNameDisplay, style: GoogleFonts.raleway( - textStyle: context.theme.textTheme.headline3.copyWith( + textStyle: context.theme.textTheme.headline3?.copyWith( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 48, @@ -43,12 +43,14 @@ class TabletLayout extends StatelessWidget { margin: const EdgeInsets.symmetric(vertical: 32), child: Row( children: [ - Expanded(child: Container(height: 1, color: Colors.white60)), + Expanded( + child: Container(height: 1, color: Colors.white60)), Container( margin: const EdgeInsets.symmetric(horizontal: 16), child: Text('OR'), ), - Expanded(child: Container(height: 1, color: Colors.white60)), + Expanded( + child: Container(height: 1, color: Colors.white60)), ], ), ), @@ -69,18 +71,19 @@ class TabletLayout extends StatelessWidget { height: 24, ), onPressed: () { - context.bloc().add(LoginWithGooglePressed()); + context.read().add(LoginWithGooglePressed()); }, ), ), Container( width: 400, - margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), + margin: + const EdgeInsets.symmetric(horizontal: 24, vertical: 24), child: AuthButton.withIcon( text: context.strings.actionSignInAnonymously, iconData: MdiIcons.incognito, onPressed: () { - context.bloc().add(LoginAnonymouslyPressed()); + context.read().add(LoginAnonymouslyPressed()); }, ), ), diff --git a/lib/ui/signin/sign_in_screen.dart b/lib/ui/signin/sign_in_screen.dart index aa5a4bf..ef002a9 100644 --- a/lib/ui/signin/sign_in_screen.dart +++ b/lib/ui/signin/sign_in_screen.dart @@ -1,15 +1,15 @@ -import 'dart:io'; +// import 'dart:io'; import 'package:appsagainsthumanity/authentication_bloc/authentication_bloc.dart'; import 'package:appsagainsthumanity/internal.dart'; import 'package:appsagainsthumanity/ui/signin/bloc/bloc.dart'; -import 'package:appsagainsthumanity/ui/signin/widgets/apple_sign_in.dart'; -import 'package:appsagainsthumanity/ui/signin/widgets/auth_button.dart'; +// import 'package:appsagainsthumanity/ui/signin/widgets/apple_sign_in.dart'; +// import 'package:appsagainsthumanity/ui/signin/widgets/auth_button.dart'; import 'package:appsagainsthumanity/ui/widgets/reponsive_widget_mediator.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +// import 'package:google_fonts/google_fonts.dart'; +// import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'layouts/mobile_layout.dart'; import 'layouts/tablet_layout.dart'; @@ -29,7 +29,7 @@ class _SignInScreen extends State { top: context.paddingTop, ), child: BlocProvider( - create: (context) => SignInBloc(userRepository: context.repository()), + create: (context) => SignInBloc(userRepository: context.read()), child: _buildBody(), ), ), @@ -52,7 +52,7 @@ class _SignInScreen extends State { } if (state.isSuccess) { - context.bloc().add(LoggedIn()); + context.read().add(LoggedIn()); } }, builder: (context, state) { diff --git a/lib/ui/signin/widgets/apple_sign_in.dart b/lib/ui/signin/widgets/apple_sign_in.dart index 9f6559e..ecff562 100644 --- a/lib/ui/signin/widgets/apple_sign_in.dart +++ b/lib/ui/signin/widgets/apple_sign_in.dart @@ -18,7 +18,7 @@ class AppleSignIn extends StatelessWidget { iconAlignment: IconAlignment.left, height: 48, onPressed: () { - context.bloc().add(LoginWithApplePressed()); + context.read().add(LoginWithApplePressed()); }, ); } else { diff --git a/lib/ui/signin/widgets/auth_button.dart b/lib/ui/signin/widgets/auth_button.dart index 5f6d3a3..6f9fa72 100644 --- a/lib/ui/signin/widgets/auth_button.dart +++ b/lib/ui/signin/widgets/auth_button.dart @@ -2,25 +2,24 @@ import 'package:flutter/material.dart'; import 'package:appsagainsthumanity/internal.dart'; class AuthButton extends StatelessWidget { - final String text; final Widget icon; final VoidCallback onPressed; const AuthButton({ - @required this.text, - @required this.icon, - @required this.onPressed, + required this.text, + required this.icon, + required this.onPressed, }); AuthButton.withIcon({ - @required this.text, - @required IconData iconData, - @required this.onPressed, + required this.text, + required IconData iconData, + required this.onPressed, }) : icon = Icon( - iconData, - color: Colors.black87, - ); + iconData, + color: Colors.black87, + ); @override Widget build(BuildContext context) { @@ -45,7 +44,7 @@ class AuthButton extends StatelessWidget { child: Text( text, textAlign: TextAlign.center, - style: context.theme.textTheme.subtitle1.copyWith( + style: context.theme.textTheme.subtitle1?.copyWith( color: Colors.black87, fontWeight: FontWeight.w600, ), diff --git a/lib/ui/signin/widgets/email_auth_form.dart b/lib/ui/signin/widgets/email_auth_form.dart index e60d90c..a0e17c4 100644 --- a/lib/ui/signin/widgets/email_auth_form.dart +++ b/lib/ui/signin/widgets/email_auth_form.dart @@ -61,7 +61,7 @@ class _EmailAuthFormState extends State { keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, validator: (value) { - if (!EmailValidator.validate(value)) { + if (!EmailValidator.validate(value!)) { return context.strings.errorInvalidEmailAddress; } return null; @@ -86,7 +86,9 @@ class _EmailAuthFormState extends State { prefixIcon: Icon(Icons.vpn_key), suffixIcon: IconButton( icon: Icon( - _isPasswordObscured ? Icons.visibility_rounded : Icons.visibility_off_rounded, + _isPasswordObscured + ? Icons.visibility_rounded + : Icons.visibility_off_rounded, ), onPressed: () { setState(() { @@ -99,7 +101,7 @@ class _EmailAuthFormState extends State { textInputAction: TextInputAction.next, obscureText: _isPasswordObscured, validator: (value) { - if (value.length < 8) { + if (value!.length < 8) { return context.strings.errorInvalidPasswordLength; } return null; @@ -128,11 +130,14 @@ class _EmailAuthFormState extends State { prefixIcon: Icon(Icons.vpn_key), suffixIcon: IconButton( icon: Icon( - _isConfirmPasswordObscured ? Icons.visibility_rounded : Icons.visibility_off_rounded, + _isConfirmPasswordObscured + ? Icons.visibility_rounded + : Icons.visibility_off_rounded, ), onPressed: () { setState(() { - _isConfirmPasswordObscured = !_isConfirmPasswordObscured; + _isConfirmPasswordObscured = + !_isConfirmPasswordObscured; }); }, ), @@ -141,7 +146,7 @@ class _EmailAuthFormState extends State { textInputAction: TextInputAction.next, obscureText: _isConfirmPasswordObscured, validator: (value) { - if (value.length < 8) { + if (value!.length < 8) { return context.strings.errorInvalidPasswordLength; } else if (value != _passwordController.text) { return context.strings.errorMismatchingPasswords; @@ -171,7 +176,7 @@ class _EmailAuthFormState extends State { keyboardType: TextInputType.name, textInputAction: TextInputAction.next, validator: (value) { - if (value.isEmpty) { + if (value!.isEmpty) { return context.strings.errorInvalidUserName; } return null; @@ -206,7 +211,7 @@ class _EmailAuthFormState extends State { padding: const EdgeInsets.symmetric(vertical: 20), ), onPressed: () { - if (_formKey.currentState.validate()) { + if (_formKey.currentState!.validate()) { _authenticateWithEmail(context); } }, @@ -219,8 +224,8 @@ class _EmailAuthFormState extends State { child: TextButton( child: Text( _authState == EmailAuthState.signIn - ? context.strings.actionEmailAltSignUp - : context.strings.actionEmailAltSignIn, + ? context.strings.actionEmailAltSignUp + : context.strings.actionEmailAltSignIn, style: GoogleFonts.raleway( color: Colors.white60, fontSize: 14, @@ -249,15 +254,17 @@ class _EmailAuthFormState extends State { ), ); } - + void _authenticateWithEmail(BuildContext context) { final email = _emailController.text.trim(); final password = _passwordController.text.trim(); if (_authState == EmailAuthState.signIn) { - context.bloc().add(LoginWithEmailPressed(email, password)); + context.read().add(LoginWithEmailPressed(email, password)); } else { final userName = _userNameController.text.trim(); - context.bloc().add(SignUpWithEmailPressed(email, password, userName)); + context + .read() + .add(SignUpWithEmailPressed(email, password, userName)); } } } diff --git a/lib/ui/terms_screen.dart b/lib/ui/terms_screen.dart index f82d953..2816905 100644 --- a/lib/ui/terms_screen.dart +++ b/lib/ui/terms_screen.dart @@ -2,9 +2,11 @@ import 'dart:async'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:appsagainsthumanity/authentication_bloc/authentication_bloc.dart'; +// import 'package:appsagainsthumanity/config.json' as Config; import 'package:appsagainsthumanity/internal.dart'; import 'package:easy_web_view/easy_web_view.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:webview_flutter/webview_flutter.dart'; @@ -14,7 +16,7 @@ class TermsOfServiceScreen extends StatefulWidget { } class _TermsOfServiceScreenState extends State { - WebViewController webViewController; + late WebViewController webViewController; final StreamController _loadingStream = StreamController.broadcast(); @@ -34,8 +36,11 @@ class _TermsOfServiceScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - brightness: Brightness.dark, - textTheme: context.theme.textTheme, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarBrightness: Brightness.dark, + ), + // brightness: Brightness.dark, + // textTheme: context.theme.textTheme, iconTheme: context.theme.iconTheme, title: Text("Terms of service"), ), @@ -55,7 +60,6 @@ class _TermsOfServiceScreenState extends State { }, ), ), - StreamBuilder( stream: _loadingStream.stream, builder: (context, snapshot) { @@ -71,9 +75,7 @@ class _TermsOfServiceScreenState extends State { } else { return Container(); } - } - ), - + }), Align( alignment: Alignment.bottomCenter, child: Container( @@ -90,9 +92,9 @@ class _TermsOfServiceScreenState extends State { ), ), onPressed: () { - Analytics().logSelectContent(contentType: 'action', itemId: "terms_of_service"); - context.bloc() - .add(AgreeToTerms()); + Analytics().logSelectContent( + contentType: 'action', itemId: "terms_of_service"); + context.read().add(AgreeToTerms()); }, ), ), @@ -102,8 +104,8 @@ class _TermsOfServiceScreenState extends State { ); } - void _loadTermsOfServiceFromUrl() async { - await webViewController.loadUrl(Config.termsOfServiceUrl); - _loadingStream.add(false); - } + // void _loadTermsOfServiceFromUrl() async { + // await webViewController.loadUrl(Config.termsOfServiceUrl); + // _loadingStream.add(false); + // } } diff --git a/lib/ui/widgets/player_circle_avatar.dart b/lib/ui/widgets/player_circle_avatar.dart index c24dc5b..fd6cc39 100644 --- a/lib/ui/widgets/player_circle_avatar.dart +++ b/lib/ui/widgets/player_circle_avatar.dart @@ -8,7 +8,7 @@ class PlayerCircleAvatar extends StatelessWidget { String get playerInitials { var splitName = player.name.split(' '); - if (splitName != null && splitName.isNotEmpty) { + if (splitName != "" && splitName.isNotEmpty) { var nonEmptyCharacters = splitName.where((e) => e.isNotEmpty); if (nonEmptyCharacters.isNotEmpty) { return nonEmptyCharacters.map((e) => e[0]).join().toUpperCase(); @@ -20,7 +20,10 @@ class PlayerCircleAvatar extends StatelessWidget { } } - PlayerCircleAvatar({@required this.player, this.radius = 20}); + PlayerCircleAvatar({ + required this.player, + this.radius = 20, + }); @override Widget build(BuildContext context) { @@ -31,18 +34,20 @@ class PlayerCircleAvatar extends StatelessWidget { ) : CircleAvatar( radius: this.radius, - backgroundImage: player.avatarUrl != null ? NetworkImage(player.avatarUrl) : null, + backgroundImage: player.avatarUrl != "" + ? NetworkImage(player.avatarUrl) + : NetworkImage(""), backgroundColor: AppColors.primary, - child: player.avatarUrl == null - ? player.name != null + child: player.avatarUrl == "" + ? player.name != "" ? Text( playerInitials, - style: context.theme.textTheme.subtitle1.copyWith( + style: context.theme.textTheme.subtitle1?.copyWith( color: Colors.white, ), ) - : null - : null, + : const SizedBox() + : const SizedBox(), ); } } diff --git a/lib/ui/widgets/reponsive_widget_mediator.dart b/lib/ui/widgets/reponsive_widget_mediator.dart index aaeede3..e10e7ee 100644 --- a/lib/ui/widgets/reponsive_widget_mediator.dart +++ b/lib/ui/widgets/reponsive_widget_mediator.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; class ResponsiveWidgetMediator extends StatelessWidget { - final WidgetBuilder mobile; final WidgetBuilder tablet; const ResponsiveWidgetMediator({ - @required this.mobile, - @required this.tablet, + required this.mobile, + required this.tablet, }); @override diff --git a/lib/ui/widgets/web_screen.dart b/lib/ui/widgets/web_screen.dart index ba320b8..438d411 100644 --- a/lib/ui/widgets/web_screen.dart +++ b/lib/ui/widgets/web_screen.dart @@ -6,8 +6,8 @@ class WebScreen extends StatelessWidget { final String url; WebScreen({ - @required this.title, - @required this.url, + required this.title, + required this.url, }); @override diff --git a/pubspec.lock b/pubspec.lock index 70271a3..4e3d8fd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,14 +7,14 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "14.0.0" + version: "31.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.41.2" + version: "2.8.0" args: dependency: transitive description: @@ -28,21 +28,21 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.8.2" bloc: dependency: "direct main" description: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "8.0.3" bloc_test: dependency: "direct dev" description: name: bloc_test url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "9.0.3" boolean_selector: dependency: transitive description: @@ -56,42 +56,42 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "2.3.0" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.6" + version: "1.0.0" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.10" + version: "3.1.0" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.5.3" + version: "2.0.6" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.11.5" + version: "2.1.10" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.1.10" + version: "7.2.3" built_collection: dependency: transitive description: @@ -105,28 +105,28 @@ packages: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.0.3" + version: "8.2.3" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" checked_yaml: dependency: transitive description: name: checked_yaml url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" cli_util: dependency: transitive description: @@ -154,49 +154,49 @@ packages: name: cloud_firestore url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "3.1.13" cloud_firestore_platform_interface: dependency: transitive description: name: cloud_firestore_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "5.5.4" cloud_firestore_web: dependency: transitive description: name: cloud_firestore_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.6.13" cloud_functions: dependency: "direct main" description: name: cloud_functions url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "3.2.13" cloud_functions_platform_interface: dependency: transitive description: name: cloud_functions_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "5.0.0" + version: "5.1.4" cloud_functions_web: dependency: transitive description: name: cloud_functions_web url: "https://pub.dartlang.org" source: hosted - version: "4.0.1" + version: "4.2.12" code_builder: dependency: transitive description: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.7.0" + version: "4.1.0" collection: dependency: transitive description: @@ -217,7 +217,14 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "0.15.2" + version: "1.0.3" + cross_file: + dependency: transitive + description: + name: cross_file + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.2" crypto: dependency: transitive description: @@ -238,21 +245,35 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.12" + version: "2.2.1" dartx: dependency: "direct main" description: name: dartx url: "https://pub.dartlang.org" source: hosted - version: "0.7.0" + version: "1.1.0" device_info: dependency: "direct main" description: name: device_info url: "https://pub.dartlang.org" source: hosted - version: "0.4.2+6" + version: "2.0.3" + device_info_platform_interface: + dependency: transitive + description: + name: device_info_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.1" easy_web_view: dependency: "direct main" description: @@ -273,7 +294,7 @@ packages: name: equatable url: "https://pub.dartlang.org" source: hosted - version: "1.2.6" + version: "2.0.3" fake_async: dependency: transitive description: @@ -295,125 +316,125 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.0" - firebase: - dependency: transitive - description: - name: firebase - url: "https://pub.dartlang.org" - source: hosted - version: "9.0.0" firebase_analytics: dependency: "direct main" description: name: firebase_analytics url: "https://pub.dartlang.org" source: hosted - version: "7.1.1" + version: "9.1.6" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "3.1.4" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web url: "https://pub.dartlang.org" source: hosted - version: "0.2.0+1" + version: "0.4.0+11" firebase_auth: dependency: "direct main" description: name: firebase_auth url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "3.3.16" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "6.2.4" firebase_auth_web: dependency: transitive description: name: firebase_auth_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "3.3.13" firebase_core: dependency: "direct main" description: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.15.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.2.5" firebase_core_web: dependency: transitive description: name: firebase_core_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.6.2" firebase_dynamic_links: dependency: "direct main" description: name: firebase_dynamic_links url: "https://pub.dartlang.org" source: hosted - version: "0.8.0" + version: "4.2.1" + firebase_dynamic_links_platform_interface: + dependency: transitive + description: + name: firebase_dynamic_links_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.3" firebase_messaging: dependency: "direct main" description: name: firebase_messaging url: "https://pub.dartlang.org" source: hosted - version: "9.0.0" + version: "11.2.15" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "3.3.1" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.2.13" firebase_storage: dependency: "direct main" description: name: firebase_storage url: "https://pub.dartlang.org" source: hosted - version: "8.0.0" + version: "10.2.14" firebase_storage_platform_interface: dependency: transitive description: name: firebase_storage_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "4.1.4" firebase_storage_web: dependency: transitive description: name: firebase_storage_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "3.2.13" fixnum: dependency: transitive description: @@ -432,7 +453,7 @@ packages: name: flutter_bloc url: "https://pub.dartlang.org" source: hosted - version: "3.2.0" + version: "8.0.1" flutter_localizations: dependency: "direct main" description: flutter @@ -451,14 +472,14 @@ packages: name: flutter_plugin_android_lifecycle url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.5" flutter_signin_button: dependency: "direct main" description: name: flutter_signin_button url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "2.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -475,7 +496,14 @@ packages: name: font_awesome_flutter url: "https://pub.dartlang.org" source: hosted - version: "8.12.0" + version: "9.2.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" glob: dependency: transitive description: @@ -517,7 +545,7 @@ packages: name: graphs url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "2.1.0" html: dependency: transitive description: @@ -545,7 +573,7 @@ packages: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.2.0" http_parser: dependency: transitive description: @@ -559,21 +587,35 @@ packages: name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "0.7.3" + version: "0.8.5" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.4+11" image_picker_for_web: dependency: "direct main" description: name: image_picker_for_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.5" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.4.4" intl: dependency: "direct main" description: @@ -587,7 +629,7 @@ packages: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.5" + version: "1.0.3" js: dependency: transitive description: @@ -601,28 +643,28 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "3.1.1" + version: "4.5.0" json_serializable: dependency: "direct dev" description: name: json_serializable url: "https://pub.dartlang.org" source: hosted - version: "3.5.1" + version: "6.2.0" kt_dart: dependency: "direct main" description: name: kt_dart url: "https://pub.dartlang.org" source: hosted - version: "0.7.0+1" + version: "0.10.0" logging: dependency: "direct main" description: name: logging url: "https://pub.dartlang.org" source: hosted - version: "0.11.4" + version: "1.0.2" markdown: dependency: transitive description: @@ -636,35 +678,42 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" material_design_icons_flutter: dependency: "direct main" description: name: material_design_icons_flutter url: "https://pub.dartlang.org" source: hosted - version: "4.0.5955" + version: "5.0.6595" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.7.0" mime: dependency: transitive description: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.7" - mockito: + version: "1.0.1" + mocktail: dependency: transitive description: - name: mockito + name: mocktail url: "https://pub.dartlang.org" source: hosted - version: "4.1.4" + version: "0.3.0" nested: dependency: transitive description: @@ -678,21 +727,21 @@ packages: name: node_preamble url: "https://pub.dartlang.org" source: hosted - version: "1.4.13" + version: "2.0.1" package_config: dependency: transitive description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.0.2" package_info: dependency: "direct main" description: name: package_info url: "https://pub.dartlang.org" source: hosted - version: "0.4.3+4" + version: "2.0.2" path: dependency: "direct main" description: @@ -755,7 +804,7 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.2" pool: dependency: transitive description: @@ -776,7 +825,7 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "4.3.3" + version: "6.0.2" pub_semver: dependency: transitive description: @@ -790,7 +839,7 @@ packages: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.8" + version: "1.2.0" quiver: dependency: transitive description: @@ -804,14 +853,14 @@ packages: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.23.1" + version: "0.27.3" share: dependency: "direct main" description: name: share url: "https://pub.dartlang.org" source: hosted - version: "0.6.5+4" + version: "2.0.4" shared_preferences: dependency: "direct main" description: @@ -867,21 +916,21 @@ packages: name: shelf_packages_handler url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "3.0.0" shelf_static: dependency: transitive description: name: shelf_static url: "https://pub.dartlang.org" source: hosted - version: "0.2.9+2" + version: "1.0.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.4+1" + version: "1.0.0" sign_in_with_apple: dependency: "direct main" description: @@ -900,7 +949,14 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.10+3" + version: "1.2.2" + source_helper: + dependency: transitive + description: + name: source_helper + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.2" source_map_stack_trace: dependency: transitive description: @@ -921,7 +977,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" stack_trace: dependency: transitive description: @@ -963,21 +1019,21 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.16.5" + version: "1.19.5" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" + version: "0.4.8" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.3.15" + version: "0.4.9" time: dependency: transitive description: @@ -991,14 +1047,14 @@ packages: name: timeago url: "https://pub.dartlang.org" source: hosted - version: "2.0.30" + version: "3.2.2" timing: dependency: transitive description: name: timing url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+3" + version: "1.0.0" typed_data: dependency: transitive description: @@ -1061,7 +1117,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" vm_service: dependency: transitive description: @@ -1082,14 +1138,14 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.2.0" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol url: "https://pub.dartlang.org" source: hosted - version: "0.7.5" + version: "1.0.0" webview_flutter: dependency: "direct main" description: @@ -1110,7 +1166,7 @@ packages: name: wiredash url: "https://pub.dartlang.org" source: hosted - version: "0.6.2" + version: "0.7.1" xdg_directories: dependency: transitive description: @@ -1126,5 +1182,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=2.0.0" + dart: ">=2.16.0 <3.0.0" + flutter: ">=2.8.0" diff --git a/pubspec.yaml b/pubspec.yaml index 8458376..68b5858 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,10 +5,10 @@ description: A CaH application for both iOS and Android # pub.dev using `pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.2+10 +version: 1.1.1+11 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: @@ -18,45 +18,45 @@ dependencies: # System Packages intl: ^0.17.0 - logging: ^0.11.3+2 + logging: ^1.0.2 shared_preferences: ^2.0.4 - package_info: ^0.4.0+14 - device_info: ^0.4.1+5 + package_info: ^2.0.2 + device_info: ^2.0.3 path_provider: ^2.0.1 path: ^1.6.4 uuid: ^3.0.1 - share: ^0.6.4 - json_annotation: ^3.0.0 - wiredash: 0.6.2 + share: ^2.0.4 + json_annotation: ^4.4.0 + wiredash: ^0.7.0+1 email_validator: ^2.0.1 - dartx: ^0.7.0 + dartx: ^1.0.0 # Architecture and Tools - bloc: ^3.0.0 - flutter_bloc: ^3.2.0 - rxdart: ^0.23.1 - kt_dart: ^0.7.0+1 - equatable: ^1.1.1 + bloc: ^8.0.2 + flutter_bloc: ^8.0.1 + rxdart: ^0.27.3 + kt_dart: ^0.10.0 + equatable: ^2.0.3 # FlutterFire firebase_core: ^1.0.1 - firebase_auth: ^1.0.1 - firebase_analytics: ^7.1.1 - firebase_storage: ^8.0.0 - firebase_messaging: ^9.0.0 - firebase_dynamic_links: ^0.8.0 - cloud_firestore: ^1.0.1 - cloud_functions: ^1.0.0 + firebase_auth: ^3.3.5 + firebase_analytics: ^9.0.5 + firebase_storage: ^10.2.5 + firebase_messaging: ^11.2.5 + firebase_dynamic_links: ^4.0.4 + cloud_firestore: ^3.1.6 + cloud_functions: ^3.2.5 google_sign_in: ^5.0.0 sign_in_with_apple: ^3.0.0 # UI - material_design_icons_flutter: ^4.0.5855 - timeago: ^2.0.29 + material_design_icons_flutter: ^5.0.6595 + timeago: ^3.1.0 webview_flutter: ^2.0.2 easy_web_view: ^1.4.0-nullsafety - flutter_signin_button: ^1.1.0 - image_picker: ^0.7.3 + flutter_signin_button: ^2.0.0 + image_picker: ^0.8.4+4 image_picker_for_web: ^2.0.0 google_fonts: ^2.0.0 clipboard: ^0.1.3 @@ -64,9 +64,9 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - bloc_test: ^3.0.0 - build_runner: ^1.0.0 - json_serializable: ^3.2.0 + bloc_test: ^9.0.2 + build_runner: ^2.1.7 + json_serializable: ^6.1.4 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec