mirror of https://github.com/flutter/pinball.git
commit
17091e1cf3
@ -0,0 +1,22 @@
|
|||||||
|
name: authentication_repository
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- "packages/authentication_repository/**"
|
||||||
|
- ".github/workflows/authentication_repository.yaml"
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "packages/authentication_repository/**"
|
||||||
|
- ".github/workflows/authentication_repository.yaml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
|
||||||
|
with:
|
||||||
|
working_directory: packages/authentication_repository
|
@ -0,0 +1,39 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# VSCode related
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Web related
|
||||||
|
lib/generated_plugin_registrant.dart
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
@ -0,0 +1,11 @@
|
|||||||
|
# authentication_repository
|
||||||
|
|
||||||
|
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
|
||||||
|
[![License: MIT][license_badge]][license_link]
|
||||||
|
|
||||||
|
Repository to manage user authentication.
|
||||||
|
|
||||||
|
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||||
|
[license_link]: https://opensource.org/licenses/MIT
|
||||||
|
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
|
||||||
|
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
|
@ -0,0 +1 @@
|
|||||||
|
include: package:very_good_analysis/analysis_options.2.4.0.yaml
|
@ -0,0 +1,3 @@
|
|||||||
|
library authentication_repository;
|
||||||
|
|
||||||
|
export 'src/authentication_repository.dart';
|
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
|
||||||
|
/// {@template authentication_exception}
|
||||||
|
/// Exception for authentication repository failures.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class AuthenticationException implements Exception {
|
||||||
|
/// {@macro authentication_exception}
|
||||||
|
const AuthenticationException(this.error, this.stackTrace);
|
||||||
|
|
||||||
|
/// The error that was caught.
|
||||||
|
final Object error;
|
||||||
|
|
||||||
|
/// The Stacktrace associated with the [error].
|
||||||
|
final StackTrace stackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@template authentication_repository}
|
||||||
|
/// Repository to manage user authentication.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class AuthenticationRepository {
|
||||||
|
/// {@macro authentication_repository}
|
||||||
|
AuthenticationRepository(this._firebaseAuth);
|
||||||
|
|
||||||
|
final FirebaseAuth _firebaseAuth;
|
||||||
|
|
||||||
|
/// Sign in the existing user anonymously using [FirebaseAuth]. If the
|
||||||
|
/// authentication process can't be completed, it will throw an
|
||||||
|
/// [AuthenticationException].
|
||||||
|
Future<void> authenticateAnonymously() async {
|
||||||
|
try {
|
||||||
|
await _firebaseAuth.signInAnonymously();
|
||||||
|
} on Exception catch (error, stackTrace) {
|
||||||
|
throw AuthenticationException(error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
name: authentication_repository
|
||||||
|
description: Repository to manage user authentication.
|
||||||
|
version: 1.0.0+1
|
||||||
|
publish_to: none
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.16.0 <3.0.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
firebase_auth: ^3.3.16
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
mocktail: ^0.2.0
|
||||||
|
very_good_analysis: ^2.4.0
|
@ -0,0 +1,40 @@
|
|||||||
|
import 'package:authentication_repository/authentication_repository.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
|
class MockFirebaseAuth extends Mock implements FirebaseAuth {}
|
||||||
|
|
||||||
|
class MockUserCredential extends Mock implements UserCredential {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late FirebaseAuth firebaseAuth;
|
||||||
|
late UserCredential userCredential;
|
||||||
|
late AuthenticationRepository authenticationRepository;
|
||||||
|
|
||||||
|
group('AuthenticationRepository', () {
|
||||||
|
setUp(() {
|
||||||
|
firebaseAuth = MockFirebaseAuth();
|
||||||
|
userCredential = MockUserCredential();
|
||||||
|
authenticationRepository = AuthenticationRepository(firebaseAuth);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('authenticateAnonymously', () {
|
||||||
|
test('completes if no exception is thrown', () async {
|
||||||
|
when(() => firebaseAuth.signInAnonymously())
|
||||||
|
.thenAnswer((_) async => userCredential);
|
||||||
|
await authenticationRepository.authenticateAnonymously();
|
||||||
|
verify(() => firebaseAuth.signInAnonymously()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws AuthenticationException when firebase auth fails', () async {
|
||||||
|
when(() => firebaseAuth.signInAnonymously())
|
||||||
|
.thenThrow(Exception('oops'));
|
||||||
|
expect(
|
||||||
|
() => authenticationRepository.authenticateAnonymously(),
|
||||||
|
throwsA(isA<AuthenticationException>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -1,95 +1,105 @@
|
|||||||
import 'package:flame/components.dart';
|
import 'package:flame/components.dart';
|
||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:pinball_flame/pinball_flame.dart';
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
/// Appends a new [ContactCallbacks] to the parent.
|
/// Appends a new [ContactCallbacks] to the parent.
|
||||||
///
|
///
|
||||||
/// This is a convenience class for adding a [ContactCallbacks] to the parent.
|
/// This is a convenience class for adding a [ContactCallbacks] to the parent.
|
||||||
/// In constract with just adding a [ContactCallbacks] to the parent's body
|
/// In contrast with just assigning a [ContactCallbacks] to a userData, this
|
||||||
/// userData, this class respects the previous [ContactCallbacks] in the
|
/// class respects the previous userData.
|
||||||
/// parent's body userData, if any. Hence, it avoids overriding any previous
|
|
||||||
/// [ContactCallbacks] in the parent.
|
|
||||||
///
|
///
|
||||||
/// It does so by grouping the [ContactCallbacks] in a [_ContactCallbacksGroup],
|
/// It does so by grouping the userData in a [_UserData], and resetting the
|
||||||
/// and resetting the parent's userData accordingly.
|
/// parent's userData accordingly.
|
||||||
// TODO(alestiago): Make use of generics to infer the type of the contact.
|
// TODO(alestiago): Make use of generics to infer the type of the contact.
|
||||||
// https://github.com/VGVentures/pinball/pull/234#discussion_r859182267
|
// https://github.com/VGVentures/pinball/pull/234#discussion_r859182267
|
||||||
// TODO(alestiago): Consider if there is a need to support adjusting a fixture's
|
|
||||||
// userData.
|
|
||||||
class ContactBehavior<T extends BodyComponent> extends Component
|
class ContactBehavior<T extends BodyComponent> extends Component
|
||||||
with ContactCallbacks, ParentIsA<T> {
|
with ContactCallbacks, ParentIsA<T> {
|
||||||
|
final _fixturesUserData = <Object>{};
|
||||||
|
|
||||||
|
/// Specifies which fixtures should be considered for contact.
|
||||||
|
///
|
||||||
|
/// Fixtures are identifiable by their userData.
|
||||||
|
///
|
||||||
|
/// If no fixtures are specified, the [ContactCallbacks] is applied to the
|
||||||
|
/// entire body, hence all fixtures are considered.
|
||||||
|
void applyTo(Iterable<Object> userData) => _fixturesUserData.addAll(userData);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@mustCallSuper
|
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
final userData = parent.body.userData;
|
if (_fixturesUserData.isNotEmpty) {
|
||||||
if (userData is _ContactCallbacksGroup) {
|
for (final fixture in _targetedFixtures) {
|
||||||
userData.addContactCallbacks(this);
|
fixture.userData = _UserData.fromFixture(fixture)..add(this);
|
||||||
} else if (userData is ContactCallbacks) {
|
}
|
||||||
final contactCallbacksGroup = _ContactCallbacksGroup()
|
|
||||||
..addContactCallbacks(userData)
|
|
||||||
..addContactCallbacks(this);
|
|
||||||
parent.body.userData = contactCallbacksGroup;
|
|
||||||
} else {
|
} else {
|
||||||
parent.body.userData = this;
|
parent.body.userData = _UserData.fromBody(parent.body)..add(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Iterable<Fixture> get _targetedFixtures =>
|
||||||
|
parent.body.fixtures.where((fixture) {
|
||||||
|
if (_fixturesUserData.contains(fixture.userData)) return true;
|
||||||
|
|
||||||
|
final userData = fixture.userData;
|
||||||
|
if (userData is _UserData) {
|
||||||
|
return _fixturesUserData.contains(userData.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ContactCallbacksGroup implements ContactCallbacks {
|
class _UserData with ContactCallbacks {
|
||||||
final List<ContactCallbacks> _contactCallbacks = [];
|
_UserData._(Object? userData) : _userData = [userData];
|
||||||
|
|
||||||
|
factory _UserData._fromUserData(Object? userData) {
|
||||||
|
if (userData is _UserData) return userData;
|
||||||
|
return _UserData._(userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory _UserData.fromFixture(Fixture fixture) =>
|
||||||
|
_UserData._fromUserData(fixture.userData);
|
||||||
|
|
||||||
|
factory _UserData.fromBody(Body body) =>
|
||||||
|
_UserData._fromUserData(body.userData);
|
||||||
|
|
||||||
|
final List<Object?> _userData;
|
||||||
|
|
||||||
|
Iterable<ContactCallbacks> get _contactCallbacks =>
|
||||||
|
_userData.whereType<ContactCallbacks>();
|
||||||
|
|
||||||
|
Object? get value => _userData.first;
|
||||||
|
|
||||||
|
void add(Object? userData) => _userData.add(userData);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@mustCallSuper
|
|
||||||
void beginContact(Object other, Contact contact) {
|
void beginContact(Object other, Contact contact) {
|
||||||
onBeginContact?.call(other, contact);
|
super.beginContact(other, contact);
|
||||||
for (final callback in _contactCallbacks) {
|
for (final callback in _contactCallbacks) {
|
||||||
callback.beginContact(other, contact);
|
callback.beginContact(other, contact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@mustCallSuper
|
|
||||||
void endContact(Object other, Contact contact) {
|
void endContact(Object other, Contact contact) {
|
||||||
onEndContact?.call(other, contact);
|
super.endContact(other, contact);
|
||||||
for (final callback in _contactCallbacks) {
|
for (final callback in _contactCallbacks) {
|
||||||
callback.endContact(other, contact);
|
callback.endContact(other, contact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@mustCallSuper
|
|
||||||
void preSolve(Object other, Contact contact, Manifold oldManifold) {
|
void preSolve(Object other, Contact contact, Manifold oldManifold) {
|
||||||
onPreSolve?.call(other, contact, oldManifold);
|
super.preSolve(other, contact, oldManifold);
|
||||||
for (final callback in _contactCallbacks) {
|
for (final callback in _contactCallbacks) {
|
||||||
callback.preSolve(other, contact, oldManifold);
|
callback.preSolve(other, contact, oldManifold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@mustCallSuper
|
|
||||||
void postSolve(Object other, Contact contact, ContactImpulse impulse) {
|
void postSolve(Object other, Contact contact, ContactImpulse impulse) {
|
||||||
onPostSolve?.call(other, contact, impulse);
|
super.postSolve(other, contact, impulse);
|
||||||
for (final callback in _contactCallbacks) {
|
for (final callback in _contactCallbacks) {
|
||||||
callback.postSolve(other, contact, impulse);
|
callback.postSolve(other, contact, impulse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addContactCallbacks(ContactCallbacks callback) {
|
|
||||||
_contactCallbacks.add(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void Function(Object other, Contact contact)? onBeginContact;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void Function(Object other, Contact contact)? onEndContact;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void Function(Object other, Contact contact, ContactImpulse impulse)?
|
|
||||||
onPostSolve;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void Function(Object other, Contact contact, Manifold oldManifold)?
|
|
||||||
onPreSolve;
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in new issue