[web_dashboard] add logout (#447)

* logout wip

* Use AnimatedSwitcher, change lingo from "login" to signIn"

* add automatic sign-in

* fix flashing sign in button

* sign out of FirebaseAuth and GoogleSignIn

* formatting

* change isSignedIn() to getter

* Add error handling for sign in

* improve error handling at login screen
pull/456/head
John Ryan 5 years ago committed by GitHub
parent 46a3f2dd09
commit 8a9bcfa113
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -23,7 +23,9 @@ class AppState {
AppState(this.auth);
}
/// Creates a [DashboardApi] when the user is logged in.
/// Creates a [DashboardApi] for the given user. This allows users of this
/// widget to specify whether [MockDashboardApi] or [ApiBuilder] should be
/// created when the user logs in.
typedef DashboardApi ApiBuilder(User user);
/// An app that displays a personalized dashboard.
@ -63,41 +65,62 @@ class _DashboardAppState extends State<DashboardApp> {
return Provider.value(
value: _appState,
child: MaterialApp(
home: Builder(
builder: (context) => SignInPage(
auth: _appState.auth,
onSuccess: (user) => _handleSignIn(user, context, _appState),
),
home: SignInSwitcher(
appState: _appState,
apiBuilder: widget.apiBuilder,
),
),
);
}
}
/// Switches between showing the [SignInPage] or [HomePage], depending on
/// whether or not the user is signed in.
class SignInSwitcher extends StatefulWidget {
final AppState appState;
final ApiBuilder apiBuilder;
SignInSwitcher({
this.appState,
this.apiBuilder,
});
@override
_SignInSwitcherState createState() => _SignInSwitcherState();
}
/// Sets the DashboardApi on AppState and navigates to the home page.
void _handleSignIn(User user, BuildContext context, AppState appState) {
appState.api = widget.apiBuilder(user);
class _SignInSwitcherState extends State<SignInSwitcher> {
bool _isSignedIn = false;
_showPage(HomePage(), context);
@override
Widget build(BuildContext context) {
return AnimatedSwitcher(
switchInCurve: Curves.easeOut,
switchOutCurve: Curves.easeOut,
duration: Duration(milliseconds: 200),
child: _isSignedIn
? HomePage(
onSignOut: _handleSignOut,
)
: SignInPage(
auth: widget.appState.auth,
onSuccess: _handleSignIn,
),
);
}
/// Navigates to the home page using a fade transition.
void _showPage(Widget page, BuildContext context) {
var route = _fadeRoute(page);
Navigator.of(context).pushReplacement(route);
void _handleSignIn(User user) {
widget.appState.api = widget.apiBuilder(user);
setState(() {
_isSignedIn = true;
});
}
/// Creates a [Route] that shows [newPage] using a fade transition.
Route<FadeTransition> _fadeRoute(Widget newPage) {
return PageRouteBuilder<FadeTransition>(
pageBuilder: (context, animation, secondaryAnimation) {
return newPage;
},
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation.drive(CurveTween(curve: Curves.ease)),
child: child,
);
},
);
Future _handleSignOut() async {
await widget.appState.auth.signOut();
setState(() {
_isSignedIn = false;
});
}
}

@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
abstract class Auth {
Future<bool> get isSignedIn;
Future<User> signIn();
Future signOut();
}
@ -10,3 +11,5 @@ abstract class Auth {
abstract class User {
String get uid;
}
class SignInException implements Exception {}

@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/services.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:firebase_auth/firebase_auth.dart' hide FirebaseUser;
@ -11,9 +12,20 @@ class FirebaseAuthService implements Auth {
final GoogleSignIn _googleSignIn = GoogleSignIn();
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<bool> get isSignedIn => _googleSignIn.isSignedIn();
Future<User> signIn() async {
try {
return await _signIn();
} on PlatformException catch (e) {
print('Unable to sign in: $e');
throw SignInException();
}
}
Future<User> _signIn() async {
GoogleSignInAccount googleUser;
if (await _googleSignIn.isSignedIn()) {
if (await isSignedIn) {
googleUser = await _googleSignIn.signInSilently();
} else {
googleUser = await _googleSignIn.signIn();
@ -30,7 +42,10 @@ class FirebaseAuthService implements Auth {
}
Future<void> signOut() async {
await _auth.signOut();
await Future.wait([
_auth.signOut(),
_googleSignIn.signOut(),
]);
}
}
@ -39,3 +54,4 @@ class _FirebaseUser implements User {
_FirebaseUser(this.uid);
}

@ -2,11 +2,20 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:math';
import 'auth.dart';
class MockAuthService implements Auth {
Future<bool> get isSignedIn async => false;
@override
Future<User> signIn() async {
// Sign in will randomly fail 25% of the time.
var random = Random();
if (random.nextInt(4) == 0) {
throw SignInException();
}
return MockUser();
}

@ -10,6 +10,12 @@ import 'dashboard.dart';
import 'entries.dart';
class HomePage extends StatefulWidget {
final VoidCallback onSignOut;
HomePage({
@required this.onSignOut,
});
@override
_HomePageState createState() => _HomePageState();
}
@ -20,6 +26,17 @@ class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return AdaptiveScaffold(
title: Text('Dashboard App'),
actions: [
Padding(
padding: const EdgeInsets.all(8.0),
child: FlatButton(
textColor: Colors.white,
onPressed: () => _handleSignOut(),
child: Text('Sign Out'),
),
)
],
currentIndex: _pageIndex,
destinations: [
AdaptiveScaffoldDestination(title: 'Home', icon: Icons.home),
@ -67,7 +84,36 @@ class _HomePageState extends State<HomePage> {
}
}
Widget _pageAtIndex(int index) {
Future<void> _handleSignOut() async {
var shouldSignOut = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('Are you sure you want to sign out?'),
actions: [
FlatButton(
child: Text('No'),
onPressed: () {
Navigator.of(context).pop(false);
},
),
FlatButton(
child: Text('Yes'),
onPressed: () {
Navigator.of(context).pop(true);
},
),
],
),
);
if (!shouldSignOut) {
return;
}
widget.onSignOut();
}
static Widget _pageAtIndex(int index) {
if (index == 0) {
return DashboardPage();
}

@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
import '../auth/auth.dart';
class SignInPage extends StatefulWidget {
class SignInPage extends StatelessWidget {
final Auth auth;
final ValueChanged<User> onSuccess;
@ -16,30 +16,88 @@ class SignInPage extends StatefulWidget {
});
@override
_SignInPageState createState() => _SignInPageState();
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SignInButton(auth: auth, onSuccess: onSuccess),
),
);
}
}
class SignInButton extends StatefulWidget {
final Auth auth;
final ValueChanged<User> onSuccess;
SignInButton({
@required this.auth,
@required this.onSuccess,
});
@override
_SignInButtonState createState() => _SignInButtonState();
}
class _SignInPageState extends State<SignInPage> {
class _SignInButtonState extends State<SignInButton> {
Future<bool> _checkSignInFuture;
@override
void initState() {
super.initState();
_checkSignInFuture = _checkIfSignedIn();
}
// Check if the user is signed in. If the user is already signed in (for
// example, if they signed in and refreshed the page), invoke the `onSuccess`
// callback right away.
Future<bool> _checkIfSignedIn() async {
var alreadySignedIn = await widget.auth.isSignedIn;
if (alreadySignedIn) {
var user = await widget.auth.signIn();
widget.onSuccess(user);
}
return alreadySignedIn;
}
Future<void> _signIn() async {
try {
var user = await widget.auth.signIn();
widget.onSuccess(user);
} on SignInException {
_showError();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Sign In'),
onPressed: () async {
var user = await widget.auth.signIn();
if (user != null) {
widget.onSuccess(user);
} else {
throw ('Unable to sign in');
}
},
),
return FutureBuilder<bool>(
future: _checkSignInFuture,
builder: (context, snapshot) {
// If signed in, or the future is incomplete, show a circular
// progress indicator.
var alreadySignedIn = snapshot.data;
if (snapshot.connectionState != ConnectionState.done ||
alreadySignedIn == true) {
return CircularProgressIndicator();
}
// If sign in failed, show toast and the login button
if (snapshot.hasError) {
_showError();
}
return RaisedButton(
child: Text('Sign In with Google'),
onPressed: () => _signIn(),
);
},
);
}
void _showError() {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Unable to sign in.'),
),
);
}

@ -28,6 +28,7 @@ class AdaptiveScaffoldDestination {
/// defined in the [destinations] parameter.
class AdaptiveScaffold extends StatefulWidget {
final Widget title;
final List<Widget> actions;
final Widget body;
final int currentIndex;
final List<AdaptiveScaffoldDestination> destinations;
@ -37,6 +38,7 @@ class AdaptiveScaffold extends StatefulWidget {
AdaptiveScaffold({
this.title,
this.body,
this.actions = const [],
@required this.currentIndex,
@required this.destinations,
this.onNavigationIndexChange,
@ -80,7 +82,9 @@ class _AdaptiveScaffoldState extends State<AdaptiveScaffold> {
),
Expanded(
child: Scaffold(
appBar: AppBar(),
appBar: AppBar(
actions: widget.actions,
),
body: widget.body,
floatingActionButton: widget.floatingActionButton,
),
@ -94,6 +98,7 @@ class _AdaptiveScaffoldState extends State<AdaptiveScaffold> {
return Scaffold(
appBar: AppBar(
title: widget.title,
actions: widget.actions,
),
body: Row(
children: [
@ -126,7 +131,10 @@ class _AdaptiveScaffoldState extends State<AdaptiveScaffold> {
// Show a bottom app bar
return Scaffold(
body: widget.body,
appBar: AppBar(title: widget.title),
appBar: AppBar(
title: widget.title,
actions: widget.actions,
),
bottomNavigationBar: BottomNavigationBar(
items: [
...widget.destinations.map(

Loading…
Cancel
Save